/* Copyright 2005-2015 Alfresco Software, Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var extScope; angular.module('flowableModeler') .controller('DecisionTableDetailsCtrl', ['$rootScope', '$scope', '$translate', '$http', '$location', '$routeParams','$modal', '$timeout', '$popover', 'DecisionTableService', 'hotRegisterer', function ($rootScope, $scope, $translate, $http, $location, $routeParams, $modal, $timeout, $popover, DecisionTableService, hotRegisterer) { extScope = $scope; $scope.decisionTableMode = 'read'; // Initialize model $scope.model = { // Store the main model id, this points to the current version of a model, // even when we're showing history latestModelId: $routeParams.modelId, columnDefs: [], columnVariableIdMap: {}, readOnly: true, availableVariableTypes: ['string', 'number', 'boolean', 'date', 'collection'] }; // Hot Model init $scope.model.hotSettings = { stretchH: 'all', outsideClickDeselects: false, manualColumnResize: false, readOnly: true, disableVisualSelection: true }; var hotReadOnlyDecisionTableEditorInstance; var hitPolicies = ['FIRST', 'ANY', 'UNIQUE', 'PRIORITY', 'RULE ORDER', 'OUTPUT ORDER', 'COLLECT']; var operators = ['==', '!=', '<', '>', '>=', '<=', 'ANY OF', 'NONE OF', 'ALL OF', 'NOT ALL OF', 'IS IN', 'IS NOT IN']; var columnIdCounter = 0; var dateFormat = 'YYYY-MM-DD'; var variableUndefined = $translate.instant('DECISION-TABLE-EDITOR.EMPTY-MESSAGES.NO-VARIABLE-SELECTED'); // helper for looking up variable id by col id $scope.getVariableNameByColumnId = function (colId) { if (!colId) { return; } if ($scope.model.columnVariableIdMap[colId]) { return $scope.model.columnVariableIdMap[colId]; } else { return variableUndefined; } }; $scope.loadDecisionTable = function() { var url, decisionTableUrl; if ($routeParams.modelHistoryId) { url = FLOWABLE.APP_URL.getModelHistoryUrl($routeParams.modelId, $routeParams.modelHistoryId); decisionTableUrl = FLOWABLE.APP_URL.getDecisionTableModelsHistoryUrl($routeParams.modelHistoryId); } else { url = FLOWABLE.APP_URL.getModelUrl($routeParams.modelId); decisionTableUrl = FLOWABLE.APP_URL.getDecisionTableModelUrl($routeParams.modelId); } $http({method: 'GET', url: url}). success(function(data, status, headers, config) { $scope.model.decisionTable = data; $scope.model.decisionTableDownloadUrl = decisionTableUrl + '/export?version=' + Date.now(); $scope.loadVersions(); }).error(function(data, status, headers, config) { $scope.returnToList(); }); }; $scope.useAsNewVersion = function() { _internalCreateModal({ template: 'views/popup/model-use-as-new-version.html', scope: $scope }, $modal, $scope); }; $scope.toggleFavorite = function() { $scope.model.favoritePending = true; var data = { favorite: !$scope.model.decisionTable.favorite }; $http({method: 'PUT', url: FLOWABLE.APP_URL.getModelUrl($scope.model.latestModelId), data: data}). success(function(data, status, headers, config) { $scope.model.favoritePending = false; if ($scope.model.decisionTable.favorite) { $scope.addAlertPromise($translate('DECISION-TABLE.ALERT.UN-FAVORITE-CONFIRM'), 'info'); } else { $scope.addAlertPromise($translate('DECISION-TABLE.ALERT.FAVORITE-CONFIRM'), 'info'); } $scope.model.decisionTable.favorite = !$scope.model.decisionTable.favorite; }).error(function(data, status, headers, config) { $scope.model.favoritePending = false; }); }; $scope.loadVersions = function() { var params = { includeLatestVersion: !$scope.model.decisionTable.latestVersion }; $http({method: 'GET', url: FLOWABLE.APP_URL.getModelHistoriesUrl($scope.model.latestModelId), params: params}). success(function(data, status, headers, config) { if ($scope.model.decisionTable.latestVersion) { if (!data.data) { data.data = []; } data.data.unshift($scope.model.decisionTable); } $scope.model.versions = data; }); }; $scope.showVersion = function(version) { if (version) { if (version.latestVersion) { $location.path("/decision-tables/" + $scope.model.latestModelId); } else { // Show latest version, no history-suffix needed in URL $location.path("/decision-tables/" + $scope.model.latestModelId + "/history/" + version.id); } } }; $scope.returnToList = function() { $location.path("/decision-tables/"); }; $scope.editDecisionTable = function() { _internalCreateModal({ template: 'views/popup/model-edit.html', scope: $scope }, $modal, $scope); }; $scope.duplicateDecisionTable = function() { var modalInstance = _internalCreateModal({ template: 'views/popup/decision-table-duplicate.html?version=' + Date.now() }, $modal, $scope); modalInstance.$scope.originalModel = $scope.model; modalInstance.$scope.duplicateDecisionTableCallback = function(result) { $rootScope.editorHistory = []; $location.url("/decision-table-editor/" + encodeURIComponent(result.id)); }; }; $scope.deleteDecisionTable = function() { _internalCreateModal({ template: 'views/popup/model-delete.html', scope: $scope }, $modal, $scope); }; $scope.shareDecisionTable = function() { _internalCreateModal({ template: 'views/popup/model-share.html', scope: $scope }, $modal, $scope); }; $scope.openEditor = function() { if ($scope.model.decisionTable) { $location.path("/decision-table-editor/" + $scope.model.decisionTable.id); } }; $scope.toggleHistory = function($event) { if (!$scope.historyState) { var state = {}; $scope.historyState = state; // Create popover state.popover = $popover(angular.element($event.target), { template: 'views/popover/history.html', placement: 'bottom-right', show: true, scope: $scope, container: 'body' }); var destroy = function() { state.popover.destroy(); delete $scope.historyState; }; // When popup is hidden or scope is destroyed, hide popup state.popover.$scope.$on('tooltip.hide', destroy); $scope.$on('$destroy', destroy); } }; $scope.doAfterGetColHeader = function (col, TH) { if ($scope.model.columnDefs[col] && $scope.model.columnDefs[col].expressionType === 'input-operator') { TH.className += "input-operator-header"; } else if ($scope.model.columnDefs[col] && $scope.model.columnDefs[col].expressionType === 'input-expression') { TH.className += "input-expression-header"; if ($scope.model.startOutputExpression - 1 === col) { TH.className += " last"; } } else if ($scope.model.columnDefs[col] && $scope.model.columnDefs[col].expressionType === 'output') { TH.className += "output-header"; if ($scope.model.startOutputExpression === col) { TH.className += " first"; } } }; $scope.doAfterModifyColWidth = function (width, col) { if ($scope.model.columnDefs[col] && $scope.model.columnDefs[col].width) { var settingsWidth = $scope.model.columnDefs[col].width; if (settingsWidth > width) { return settingsWidth; } } return width; }; $scope.doAfterRender = function () { var element = document.querySelector("thead > tr > th:first-of-type"); if (element) { var firstChild = element.firstChild; var newElement = angular.element('
'); element.className = 'hit-policy-container'; element.replaceChild(newElement[0], firstChild); } $timeout(function () { hotReadOnlyDecisionTableEditorInstance = hotRegisterer.getInstance('read-only-decision-table-editor'); if (hotReadOnlyDecisionTableEditorInstance) { hotReadOnlyDecisionTableEditorInstance.validateCells(); } }); }; $scope.doAfterValidate = function (isValid, value, row, prop, source) { if (isCorrespondingCollectionOperator(row, prop)) { return true; } else if (isCustomExpression(value) || isDashValue(value)) { disableCorrespondingOperatorCell(row, prop); return true; } else { enableCorrespondingOperatorCell(row, prop); } }; var isCustomExpression = function (val) { return !!(val != null && (String(val).startsWith('${') || String(val).startsWith('#{'))); }; var isDashValue = function (val) { return !!(val != null && "-" === val); }; var isCorrespondingCollectionOperator = function (row, prop) { var operatorCol = getCorrespondingOperatorCell(row, prop); var operatorCellMeta = hotReadOnlyDecisionTableEditorInstance.getCellMeta(row, operatorCol); var isCollectionOperator = false; if (isOperatorCell(operatorCellMeta)) { var operatorValue = hotReadOnlyDecisionTableEditorInstance.getDataAtCell(row, operatorCol); if (operatorValue === "ANY OF" || operatorValue === "NONE OF" || operatorValue === "ALL OF" || operatorValue === "NOT ALL OF" || operatorValue === "IS IN" || operatorValue === "IS NOT IN") { isCollectionOperator = true; } } return isCollectionOperator; }; var disableCorrespondingOperatorCell = function (row, prop) { var operatorCol = getCorrespondingOperatorCell(row, prop); var operatorCellMeta = hotReadOnlyDecisionTableEditorInstance.getCellMeta(row, operatorCol); if (!isOperatorCell(operatorCellMeta)) { return; } if (operatorCellMeta.className != null && operatorCellMeta.className.indexOf('custom-expression-operator') !== -1) { return; } var currentEditor = hotReadOnlyDecisionTableEditorInstance.getCellEditor(row, operatorCol); hotReadOnlyDecisionTableEditorInstance.setCellMeta(row, operatorCol, 'className', operatorCellMeta.className + ' custom-expression-operator'); hotReadOnlyDecisionTableEditorInstance.setCellMeta(row, operatorCol, 'originalEditor', currentEditor); hotReadOnlyDecisionTableEditorInstance.setCellMeta(row, operatorCol, 'editor', false); hotReadOnlyDecisionTableEditorInstance.setDataAtCell(row, operatorCol, null); }; var enableCorrespondingOperatorCell = function (row, prop) { var operatorCol = getCorrespondingOperatorCell(row, prop); var operatorCellMeta = hotReadOnlyDecisionTableEditorInstance.getCellMeta(row, operatorCol); if (!isOperatorCell(operatorCellMeta)) { return; } if (operatorCellMeta == null || operatorCellMeta.className == null || operatorCellMeta.className.indexOf('custom-expression-operator') == -1) { return; } operatorCellMeta.className = operatorCellMeta.className.replace('custom-expression-operator', ''); hotReadOnlyDecisionTableEditorInstance.setCellMeta(row, operatorCol, 'className', operatorCellMeta.className); hotReadOnlyDecisionTableEditorInstance.setCellMeta(row, operatorCol, 'editor', operatorCellMeta.originalEditor); hotReadOnlyDecisionTableEditorInstance.setDataAtCell(row, operatorCol, '=='); }; var getCorrespondingOperatorCell = function (row, prop) { var currentCol = hotReadOnlyDecisionTableEditorInstance.propToCol(prop); if (currentCol < 1) { return; } var operatorCol = currentCol - 1; return operatorCol; }; var isOperatorCell = function (cellMeta) { return !(cellMeta == null || cellMeta.prop == null || typeof cellMeta.prop !== 'string'|| cellMeta.prop.endsWith("_operator") === false); }; var createNewInputExpression = function (inputExpression) { var newInputExpression; if (inputExpression) { newInputExpression = { id: _generateColumnId(), label: inputExpression.label, variableId: inputExpression.variableId, type: inputExpression.type, newVariable: inputExpression.newVariable, entries: inputExpression.entries }; } else { newInputExpression = { id: _generateColumnId(), label: null, variableId: null, type: null, newVariable: null, entries: null }; } return newInputExpression; }; $scope.openHitPolicyEditor = function () { var editTemplate = 'views/popup/decision-table-edit-hit-policy.html'; $scope.model.hitPolicy = $scope.currentDecisionTable.hitIndicator; _internalCreateModal({ template: editTemplate, scope: $scope }, $modal, $scope); }; $scope.openInputExpressionEditor = function (expressionPos, newExpression) { var editTemplate = 'views/popup/decision-table-edit-input-expression.html'; $scope.model.newExpression = !!newExpression; if (!$scope.model.newExpression) { $scope.model.selectedExpression = $scope.currentDecisionTable.inputExpressions[expressionPos]; } else { if (expressionPos >= $scope.model.startOutputExpression) { $scope.model.selectedColumn = $scope.model.startOutputExpression - 1; } else { $scope.model.selectedColumn = Math.floor(expressionPos / 2); } } _internalCreateModal({ template: editTemplate, scope: $scope }, $modal, $scope); }; $scope.openOutputExpressionEditor = function (expressionPos, newExpression) { var editTemplate = 'views/popup/decision-table-edit-output-expression.html'; $scope.model.newExpression = !!newExpression; $scope.model.hitPolicy = $scope.currentDecisionTable.hitIndicator; $scope.model.selectedColumn = expressionPos; if (!$scope.model.newExpression) { $scope.model.selectedExpression = $scope.currentDecisionTable.outputExpressions[expressionPos]; } _internalCreateModal({ template: editTemplate, scope: $scope }, $modal, $scope); }; var createNewOutputExpression = function (outputExpression) { var newOutputExpression; if (outputExpression) { newOutputExpression = { id: _generateColumnId(), label: outputExpression.label, variableId: outputExpression.variableId, type: outputExpression.variableType, newVariable: outputExpression.newVariable, entries: outputExpression.entries }; } else { newOutputExpression = { id: _generateColumnId(), label: null, variableId: null, type: null, newVariable: null, entries: null }; } return newOutputExpression; }; var _loadDecisionTableDefinition = function (modelId, historyId) { DecisionTableService.fetchDecisionTableDetails(modelId, historyId).then(function (decisionTable) { $rootScope.currentDecisionTable = decisionTable.decisionTableDefinition; $rootScope.currentDecisionTable.id = decisionTable.id; $rootScope.currentDecisionTable.key = decisionTable.decisionTableDefinition.key; $rootScope.currentDecisionTable.name = decisionTable.name; $rootScope.currentDecisionTable.description = decisionTable.description; // decision table model to used in save dialog $rootScope.currentDecisionTableModel = { id: decisionTable.id, name: decisionTable.name, key: decisionTable.decisionTableDefinition.key, description: decisionTable.description }; if (!$rootScope.currentDecisionTable.hitIndicator) { $rootScope.currentDecisionTable.hitIndicator = hitPolicies[0]; } evaluateDecisionTableGrid($rootScope.currentDecisionTable); }); }; var evaluateDecisionTableGrid = function (decisionTable) { $scope.evaluateDecisionHeaders(decisionTable); evaluateDecisionGrid(decisionTable); }; var setGridValues = function (key, type) { if ($scope.model.rulesData) { $scope.model.rulesData.forEach(function (rowData) { if (type === 'input-operator') { if (!(key in rowData) || rowData[key] === '') { rowData[key] = '=='; } } // else if (type === 'input-expression') { // if (!(key in rowData) || rowData[key] === '') { // rowData[key] = '-'; // } // } }); } }; var evaluateDecisionGrid = function (decisionTable) { var tmpRuleGrid = []; // rows if (decisionTable.rules && decisionTable.rules.length > 0) { decisionTable.rules.forEach(function (rule) { // rule data var tmpRowValues = {}; for (var i = 0; i < Object.keys(rule).length; i++) { var id = Object.keys(rule)[i]; $scope.model.columnDefs.forEach(function (columnDef) { // set counter to max value var expressionId = 0; try { expressionId = parseInt(columnDef.expression.id); } catch (e) { } if (expressionId > columnIdCounter) { columnIdCounter = expressionId; } }); tmpRowValues[id] = rule[id]; } tmpRuleGrid.push(tmpRowValues); }); } else { // initialize default values tmpRuleGrid.push(createDefaultRow()); } // $rootScope.currentDecisionTableRules = tmpRuleGrid; $scope.model.rulesData = tmpRuleGrid; }; var createDefaultRow = function () { var defaultRow = {}; $scope.model.columnDefs.forEach(function (columnDefinition) { if (columnDefinition.expressionType === 'input-operator') { defaultRow[columnDefinition.data] = '=='; } // else if (columnDefinition.expressionType === 'input-expression') { // defaultRow[columnDefinition.data] = '-'; // } else if (columnDefinition.expressionType === 'output') { defaultRow[columnDefinition.data] = ''; } }); return defaultRow; }; var composeInputOperatorColumnDefinition = function (inputExpression) { var expressionPosition = $scope.currentDecisionTable.inputExpressions.indexOf(inputExpression); var columnDefinition = { data: inputExpression.id + '_operator', expressionType: 'input-operator', expression: inputExpression, width: '70', className: 'input-operator-cell', type: 'dropdown', source: operators }; return columnDefinition; }; var composeInputExpressionColumnDefinition = function (inputExpression) { var expressionPosition = $scope.currentDecisionTable.inputExpressions.indexOf(inputExpression); var type; switch (inputExpression.type) { case 'date': type = 'date'; break; case 'number': type = 'numeric'; break; case 'boolean': type = 'dropdown'; break; default: type = 'text'; } var columnDefinition = { data: inputExpression.id + '_expression', type: type, title: '