262 lines
16 KiB
JavaScript
262 lines
16 KiB
JavaScript
"use strict";
|
|
(function () {
|
|
'use strict';
|
|
angular.module('myitsmApp')
|
|
.directive('expressionBuilder', [function () {
|
|
return {
|
|
restrict: 'E',
|
|
scope: {
|
|
fieldName: '=',
|
|
expression: '=',
|
|
propertyName: '=',
|
|
fieldAcceleratorsList: '=',
|
|
onExpressionChange: '&',
|
|
isRequired: '=?'
|
|
},
|
|
templateUrl: 'views/admin/screen-configuration/expression-builder.html',
|
|
controller: ['$scope', '$element', '$filter', '$timeout', 'expressionSyntaxTreeBuilder', 'i18nService', 'screenConfigurationModel', function ($scope, $element, $filter, $timeout, expressionSyntaxTreeBuilder, i18nService, screenConfigurationModel) {
|
|
var typeAheadMode = false, gotFullMatch = false, actualTypeAheadText, expressionStart = 0, expressionEnd = 0;
|
|
var textFieldExpression = $element.find('.text-input')[0];
|
|
var keyCodes = {
|
|
enter: 13,
|
|
escape: 27,
|
|
tab: 9,
|
|
upArrow: 38,
|
|
downArrow: 40,
|
|
space: 32
|
|
};
|
|
var preventDefaultForKeys = [keyCodes.enter, keyCodes.upArrow, keyCodes.downArrow, keyCodes.tab];
|
|
$scope.accelerators = {
|
|
expression: '',
|
|
showAcceleratorsList: false,
|
|
availableExpressions: $scope.fieldAcceleratorsList
|
|
};
|
|
$scope.errorMsg = '';
|
|
function validateExpression(exp) {
|
|
var parsedExp, errorType, mapperObject, pos, params, key;
|
|
$scope.errorMsg = '';
|
|
try {
|
|
parsedExp = expressionSyntaxTreeBuilder(exp);
|
|
}
|
|
catch (e) {
|
|
mapperObject = {
|
|
errorMessage1: 'Expected expression after',
|
|
errorMessage2: 'Expected :',
|
|
errorMessage3: 'Expected expression',
|
|
errorMessage4: 'Expected exponent (',
|
|
errorMessage5: 'Variable names cannot start with a number (',
|
|
errorMessage6: 'Unexpected period',
|
|
errorMessage7: 'Unclosed quote after',
|
|
errorMessage8: 'Unexpected',
|
|
errorMessage9: 'Unclosed [',
|
|
errorMessage10: 'Unclosed (',
|
|
errorMessage11: 'Expected [',
|
|
errorMessage12: 'Expected ]',
|
|
errorMessage13: 'Expected (',
|
|
errorMessage14: 'Expected )',
|
|
errorMessage15: 'Expected comma'
|
|
};
|
|
for (key in mapperObject) {
|
|
if (_.includes(e.description, mapperObject[key])) {
|
|
errorType = key;
|
|
break;
|
|
}
|
|
}
|
|
if (mapperObject[errorType]) {
|
|
pos = e.message.substring(mapperObject[errorType].length, e.message.indexOf('at character'));
|
|
}
|
|
params = [pos, e.index];
|
|
if (errorType) {
|
|
$scope.errorMsg = i18nService.getLocalizedStringwithParams('expression.builder.' + errorType, params);
|
|
}
|
|
else {
|
|
$scope.errorMsg = i18nService.getLocalizedString('expression.builder.standard.error');
|
|
}
|
|
}
|
|
if (!$scope.errorMsg) {
|
|
$scope.errorMsg = screenConfigurationModel.checkExpressionValid(parsedExp, $scope.fieldAcceleratorsList);
|
|
}
|
|
}
|
|
if ($scope.expression) {
|
|
validateExpression($scope.expression);
|
|
}
|
|
$scope.handleBodyKeyDown = function ($event) {
|
|
if (($event.which === 52 || $event.keyCode === 52 || $event.key === '$')) {
|
|
$scope.accelerators.showAcceleratorsList = true;
|
|
expressionStart = $event.target.selectionStart;
|
|
typeAheadMode = true;
|
|
}
|
|
if ($scope.accelerators.showAcceleratorsList && (preventDefaultForKeys.indexOf($event.keyCode) >= 0 || ($event.keyCode === keyCodes.space && gotFullMatch))) {
|
|
$event.preventDefault();
|
|
}
|
|
};
|
|
$scope.handleBodyChange = function ($event) {
|
|
var actionText = textFieldExpression.value;
|
|
var pos = -1, flag = false, temp;
|
|
if (!$scope.accelerators.showAcceleratorsList) {
|
|
for (temp = 0; temp < actionText.length; temp++) {
|
|
if (actionText[temp] === '$' && !flag) {
|
|
flag = true;
|
|
pos = temp;
|
|
}
|
|
else if (actionText[temp] === ' ' && flag) {
|
|
flag = false;
|
|
pos = -1;
|
|
}
|
|
}
|
|
if (pos > -1) {
|
|
$scope.accelerators.showAcceleratorsList = true;
|
|
expressionStart = pos;
|
|
typeAheadMode = true;
|
|
if (preventDefaultForKeys.indexOf($event.keyCode) >= 0 || ($event.keyCode === keyCodes.space && gotFullMatch)) {
|
|
$event.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
expressionEnd = $event.target.selectionEnd;
|
|
if (textFieldExpression.value.length > 1 && typeAheadMode && (expressionStart > expressionEnd)) {
|
|
$scope.accelerators.showAcceleratorsList = false;
|
|
return;
|
|
}
|
|
if ($scope.accelerators.showAcceleratorsList) {
|
|
if (gotFullMatch && [keyCodes.tab, keyCodes.rightArrow, keyCodes.space].indexOf($event.keyCode) >= 0) {
|
|
gotFullMatch = false;
|
|
return;
|
|
}
|
|
if (typeAheadMode && ($event.keyCode === keyCodes.tab)) {
|
|
var urlText = textFieldExpression.value;
|
|
($scope.typeAheadListPos === $scope.acceleratorsList.length - 1) ? $scope.typeAheadListPos = 0 : $scope.typeAheadListPos++;
|
|
var selectedItem = $scope.acceleratorsList[$scope.typeAheadListPos] ? $scope.acceleratorsList[$scope.typeAheadListPos].name : '';
|
|
var updatedText = [urlText.slice(0, expressionStart), selectedItem, urlText.slice(expressionEnd)].join('');
|
|
expressionEnd = parseInt(expressionStart + selectedItem.length);
|
|
actualTypeAheadText = updatedText.substring(expressionStart, expressionEnd);
|
|
textFieldExpression.value = updatedText;
|
|
$timeout(function () {
|
|
textFieldExpression.setSelectionRange(expressionStart, expressionEnd);
|
|
textFieldExpression.selectionStart = expressionStart;
|
|
textFieldExpression.selectionEnd = expressionEnd;
|
|
});
|
|
return;
|
|
}
|
|
// ignore if control or alternate key was pressed
|
|
if ($event.ctrlKey === true || $event.altKey === true || $event.keyCode === keyCodes.leftArrow || $event.keyCode === keyCodes.rightArrow) {
|
|
return;
|
|
}
|
|
else if ($event.keyCode === keyCodes.upArrow) {
|
|
if ($scope.typeAheadListPos > 0) {
|
|
$scope.typeAheadListPos--;
|
|
}
|
|
return;
|
|
}
|
|
else if ($event.keyCode === keyCodes.downArrow) {
|
|
if ($scope.typeAheadListPos < ($scope.acceleratorsList.length - 1)) {
|
|
$scope.typeAheadListPos++;
|
|
}
|
|
return;
|
|
}
|
|
else if ($event.keyCode === keyCodes.enter) {
|
|
$scope.accelerators.expression = actualTypeAheadText;
|
|
$scope.insertAcceleratorText();
|
|
return;
|
|
}
|
|
else if ($event.keyCode === keyCodes.escape) {
|
|
$scope.disableTypeAheadMode();
|
|
return;
|
|
}
|
|
}
|
|
if ($scope.expression && $scope.expression.length) {
|
|
if (typeAheadMode) {
|
|
actualTypeAheadText = textFieldExpression.value.substring(expressionStart, expressionEnd);
|
|
$scope.typeAheadListPos = 0;
|
|
}
|
|
}
|
|
if (typeAheadMode) {
|
|
if (actualTypeAheadText && actualTypeAheadText.length > 0 && actualTypeAheadText !== $scope.accelerators.expression) {
|
|
$scope.acceleratorsList = $filter('filter')($scope.accelerators.availableExpressions, { name: actualTypeAheadText.substring(1) });
|
|
if ($scope.acceleratorsList.length > 0 || $scope.accelerators.expression.length === 0) {
|
|
($scope.acceleratorsList.length > 0) ? $scope.accelerators.showAcceleratorsList = true : angular.noop();
|
|
$scope.accelerators.expression = actualTypeAheadText;
|
|
$scope.popupLeftPosition = 0;
|
|
$scope.popupTopPosition = 0;
|
|
}
|
|
else if ($scope.acceleratorsList.length === 0 && $scope.accelerators.expression.length > 1) {
|
|
$scope.hideTypeAheadPopup();
|
|
}
|
|
}
|
|
else if (actualTypeAheadText === '$' || textFieldExpression.value === '$') {
|
|
$scope.acceleratorsList = $scope.accelerators.availableExpressions;
|
|
}
|
|
}
|
|
};
|
|
$scope.handleBodyClick = function ($event) {
|
|
if ($event.target.value.length > 1 && $scope.accelerators.showAcceleratorsList && expressionStart > expressionEnd) {
|
|
$scope.accelerators.showAcceleratorsList = false;
|
|
}
|
|
};
|
|
$scope.acceleratorMouseover = function ($index) {
|
|
$scope.typeAheadListPos = $index;
|
|
};
|
|
$scope.insertAcceleratorText = function (selectedItem) {
|
|
var urlText = textFieldExpression.value, accelerator = selectedItem || $scope.acceleratorsList[$scope.typeAheadListPos], insertText = accelerator.name, updatedText = urlText;
|
|
if ($scope.accelerators.expression) {
|
|
if (expressionStart === 0) {
|
|
updatedText = insertText + ' ';
|
|
}
|
|
else {
|
|
if (expressionEnd < urlText.length) {
|
|
updatedText = [urlText.slice(0, expressionStart), insertText + ' ', urlText.slice(expressionEnd)].join('');
|
|
}
|
|
else if (expressionEnd === urlText.length) {
|
|
updatedText = urlText.substr(0, urlText.length - actualTypeAheadText.length) + insertText + ' ';
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (urlText.length === 0) {
|
|
updatedText = insertText;
|
|
}
|
|
else {
|
|
if (expressionEnd > 0) {
|
|
updatedText = [urlText.slice(0, expressionStart), insertText + ' ', urlText.slice(expressionEnd)].join('');
|
|
}
|
|
else {
|
|
updatedText += insertText + ' ';
|
|
}
|
|
}
|
|
}
|
|
$scope.disableTypeAheadMode();
|
|
$scope.expression = updatedText;
|
|
$scope.handleExpressionChange();
|
|
};
|
|
$scope.hideTypeAheadPopup = function () {
|
|
$scope.accelerators.showAcceleratorsList = false;
|
|
};
|
|
$scope.disableTypeAheadMode = function () {
|
|
actualTypeAheadText = '';
|
|
$scope.accelerators.expression = '';
|
|
$scope.accelerators.showAcceleratorsList = false;
|
|
$scope.typeAheadListPos = 0;
|
|
$scope.acceleratorsList = null;
|
|
typeAheadMode = false;
|
|
expressionStart = 0;
|
|
expressionEnd = 0;
|
|
};
|
|
$scope.acceleratorsHelp = function () {
|
|
actualTypeAheadText = '$';
|
|
$scope.accelerators.expression = '';
|
|
$scope.accelerators.showAcceleratorsList = true;
|
|
$scope.typeAheadListPos = 0;
|
|
$scope.acceleratorsList = $scope.accelerators.availableExpressions;
|
|
expressionStart = $scope.expression.length;
|
|
};
|
|
$scope.handleExpressionChange = function () {
|
|
validateExpression($scope.expression);
|
|
if (angular.isFunction($scope.onExpressionChange)) {
|
|
$scope.onExpressionChange();
|
|
}
|
|
};
|
|
}]
|
|
};
|
|
}]);
|
|
}());
|