电子档案
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1356 lines
51 KiB

  1. /* Licensed under the Apache License, Version 2.0 (the "License");
  2. * you may not use this file except in compliance with the License.
  3. * You may obtain a copy of the License at
  4. *
  5. * http://www.apache.org/licenses/LICENSE-2.0
  6. *
  7. * Unless required by applicable law or agreed to in writing, software
  8. * distributed under the License is distributed on an "AS IS" BASIS,
  9. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. * See the License for the specific language governing permissions and
  11. * limitations under the License.
  12. */
  13. flowableModule
  14. .directive('restrictInput', ["$parse", function ($parse) {
  15. return {
  16. restrict: 'A',
  17. require: 'ngModel',
  18. priority: 1002,
  19. link: function postLink(scope, elm, attrs, ctrl) {
  20. var acceptedFormat = attrs["restrictInput"];
  21. if (acceptedFormat == undefined || acceptedFormat == null || acceptedFormat == "") {
  22. acceptedFormat = attrs["dateFormat"];
  23. }
  24. scope.field.acceptedFormat=acceptedFormat;
  25. function calculateAcceptedFormats(format) {
  26. var format1 = format.toUpperCase(); //d-m-yyyy
  27. var format2 = format1.replace(/-D-/,"-DD-").replace(/^D-/,"DD-").replace(/-D$/,"-DD"); //dd-m-yyyy
  28. var format3 = format1.replace(/-M-/,"-MM-").replace(/^M-/,"MM-").replace(/-M$/,"-MM"); //d-mm-yyyy
  29. var format4 = format2.replace(/-M-/,"-MM-").replace(/^M-/,"MM-").replace(/-M$/,"-MM"); //dd-mm-yyyy
  30. return [format1,format2,format3,format4];
  31. }
  32. var acceptedFormats = calculateAcceptedFormats(acceptedFormat);
  33. var skipValidation = false;
  34. if (acceptedFormat == undefined || acceptedFormat == null || acceptedFormat == "") {
  35. skipValidation = true;
  36. }
  37. var oldRenderer = ctrl.$render;
  38. ctrl.$render = function () {
  39. elm.val(ctrl.$viewValue);
  40. if (ctrl.$dateValue && !isNaN(ctrl.$dateValue.getTime())) {
  41. if (oldRenderer) {
  42. oldRenderer();
  43. }
  44. }
  45. };
  46. function isValidText(viewValue, format) {
  47. if (viewValue === undefined || viewValue == null || viewValue ==='') return true;
  48. if (viewValue.length > format.length) return false;
  49. for (var i = 0; i < Math.min(format.length, viewValue.length); i++) {
  50. var charType = format[i];
  51. if (charType.toUpperCase().match(/D|M|Y/)) {
  52. if (viewValue[i].match(/\d/) == null) return false;
  53. }else {
  54. if (viewValue[i] != charType) return false;
  55. }
  56. }
  57. return true;
  58. }
  59. ctrl.$parsers.unshift(function (viewValue) {
  60. if (skipValidation) return viewValue; //just skip this parser
  61. var isValid = false;
  62. for(var i =0 ; i < acceptedFormats.length && !isValid; i++){
  63. isValid |= isValidText(viewValue,acceptedFormats[i]);
  64. }
  65. if (!isValid) {
  66. //should restore to the latest known well formated date.
  67. ctrl.$dateValue = null;
  68. } else {
  69. ctrl.$lastValidText = viewValue;
  70. //by default the date picker in angular strap does not reset the dateValue if the viewValue is null or empty.
  71. if (viewValue === undefined || viewValue == null || viewValue === '') {
  72. ctrl.$dateValue = null;
  73. }
  74. return viewValue;
  75. }
  76. ctrl.$setViewValue(ctrl.$lastValidText);
  77. ctrl.$render();
  78. return ctrl.$lastValidText;
  79. });
  80. }
  81. }
  82. }
  83. ]);
  84. flowableModule
  85. .directive('autoHeight', ['$rootScope', '$timeout', function($rootScope, $timeout) {
  86. return {
  87. restrict: 'AC',
  88. scope: {
  89. 'toWatch': '=autoHeight'
  90. },
  91. compile: function (element, attr) {
  92. return function ($scope, $element, $attrs) {
  93. var offset = 0;
  94. if($attrs['offset']) {
  95. offset = parseInt($attrs['offset']);
  96. if(isNaN(offset) || offset == undefined) {
  97. offset = 0;
  98. }
  99. }
  100. var update = function($element) {
  101. // Get hold of parent and iterate all the children to get available height
  102. $timeout(function() {
  103. var total = $element.parent().outerHeight() - offset;
  104. var found = false;
  105. $element.parent().children().each(function() {
  106. if(!found) {
  107. if($element[0] == this) {
  108. found = true;
  109. } else {
  110. // Substract preceding child's height
  111. total -= angular.element(this).outerHeight();
  112. }
  113. }
  114. });
  115. if(found) {
  116. $element.height(total);
  117. }
  118. }, 0);
  119. };
  120. if($scope.unregisterWatcher) {
  121. $scope.unregisterWatcher();
  122. }
  123. $scope.unregisterWatcher = $rootScope.$watch('window.height', function(windowHeight) {
  124. update($element);
  125. });
  126. if($scope.unregisterForceWatcher) {
  127. $scope.unregisterForceWatcher();
  128. }
  129. $scope.unregisterForceWatcher = $rootScope.$watch('window.forceRefresh', function(forceValue) {
  130. update($element);
  131. });
  132. $scope.$on('$destroy', function() {
  133. // Cleanup watcher for window-height
  134. if($scope.unregisterWatcher) {
  135. $scope.unregisterWatcher();
  136. }
  137. if($scope.unregisterForceWatcher) {
  138. $scope.unregisterForceWatcher();
  139. }
  140. });
  141. }
  142. }
  143. };
  144. }]);
  145. /**
  146. * Directive that ensures the child-element with class .active is visible and scrolls if needed. Watches the value
  147. * of the directive and will re-apply if this value is changes.
  148. */
  149. flowableModule
  150. .directive('scrollToActive', ['$timeout', function($timeout) {
  151. return {
  152. restrict: 'AC',
  153. scope: {
  154. toWatch: "=scrollToActiveModel"
  155. },
  156. compile: function (element, attr) {
  157. return function ($scope, $element, $attrs) {
  158. $scope.$watch('toWatch', function() {
  159. $timeout(function() {
  160. var useParent = $attrs['useParent'];
  161. var offsetTop = $attrs['offsetTop'];
  162. if(offsetTop) {
  163. offsetTop = parseInt(offsetTop);
  164. if(isNaN(offsetTop)) {
  165. offsetTop = 0;
  166. }
  167. }
  168. if (!offsetTop) {
  169. offsetTop = 0;
  170. }
  171. var selectedArr = $element.children('.active');
  172. if(selectedArr && selectedArr.length > 0) {
  173. var selected = angular.element(selectedArr[0]);
  174. if(useParent) {
  175. $element = angular.element($element.parent());
  176. }
  177. var selectedTop = selected.position().top - $element.position().top + $element.scrollTop();
  178. var selectedBottom = selectedTop + selected.outerHeight();
  179. var elementBottom = $element.scrollTop() + $element.innerHeight();
  180. var elementTop = elementBottom - $element.innerHeight();
  181. if(selectedTop <= elementTop) {
  182. // scroll up
  183. $element.scrollTop(selectedTop - selected.outerHeight() - offsetTop);
  184. } else if(selectedBottom > elementBottom) {
  185. // scroll down
  186. $element.scrollTop(elementTop + selected.outerHeight() - offsetTop);
  187. }
  188. }
  189. }, 0);
  190. });
  191. }
  192. }
  193. };
  194. }]);
  195. /**
  196. * Directive that ensures the popup is scrolled into view, using the first parent as scroll-pane that has
  197. * a class 'scroll-container' set on it. Is applied when the popup is shown.
  198. */
  199. flowableModule
  200. .directive('autoScroll', ['$timeout', function($timeout) {
  201. return {
  202. restrict: 'AC',
  203. compile: function (element, attr) {
  204. return function ($scope, $element, $attrs) {
  205. $scope.$on('tooltip.show', function() {
  206. $timeout(function() {
  207. // Find appropriate parent
  208. var parent = $element[0];
  209. while(parent) {
  210. if(parent.className && parent.className.indexOf('scroll-container') >= 0) {
  211. break;
  212. }
  213. parent = parent.parentNode;
  214. }
  215. if(parent) {
  216. parent = angular.element(parent);
  217. var selectedTop = $element.offset().top - parent.offset().top + $element.scrollTop();
  218. var selectedBottom = selectedTop + $element.outerHeight();
  219. if(selectedBottom + 30 >= parent.outerHeight()) {
  220. parent.scrollTop(selectedTop);
  221. }
  222. }
  223. }, 50);
  224. });
  225. }
  226. }
  227. };
  228. }]);
  229. flowableModule
  230. .directive('userName', function() {
  231. var directive = {};
  232. directive.template = '{{user | username}}';
  233. directive.scope = {
  234. user: "=userName"
  235. };
  236. return directive;
  237. });
  238. /**
  239. * Executes the method that is set on the directive attribute value when ANY OTHER element is clicked, which is not the element the
  240. * directive is on, or any of it's children.
  241. *
  242. */
  243. flowableModule
  244. .directive('clickAnywhere', ["$document", "$parse", function ($document, $parse) {
  245. var linkFunction = function ($scope, $element, $attributes) {
  246. var scopeExpression = $attributes.clickAnywhere;
  247. var invoker = $parse(scopeExpression);
  248. var ignoreId = $attributes.ignore;
  249. var ignoreClass = $attributes.ignoreClass;
  250. var ignorePopupEvents = $attributes.ignorePopupEvents == 'true';
  251. var handler = function (event) {
  252. // Check source of event
  253. var parent = event.target;
  254. while(parent) {
  255. if(parent == $element[0] ||
  256. (ignoreId && parent.id == ignoreId) ||
  257. (ignoreClass && parent.className && parent.className.indexOf(ignoreClass) >= 0)) {
  258. event.stopPropagation();
  259. event.preventDefault();
  260. return;
  261. }
  262. parent = parent.parentNode;
  263. }
  264. $scope.$apply(
  265. function () {
  266. invoker($scope, {$event: event});
  267. }
  268. );
  269. };
  270. $document.on("click", handler);
  271. $scope.$on('$destroy', function () {
  272. $document.off("click", handler);
  273. });
  274. // Special handling for tooltips which don't destroy the scope
  275. var hideReg = $scope.$on('tooltip.hide', function () {
  276. if(!ignorePopupEvents) {
  277. $document.off("click", handler);
  278. hideReg();
  279. }
  280. });
  281. };
  282. // Return the linking function.
  283. return( linkFunction );
  284. }
  285. ]);
  286. flowableModule
  287. .directive('autoFocus', ['$timeout', '$parse', function($timeout, $parse) {
  288. return {
  289. restrict: 'AC',
  290. compile: function($element, attr) {
  291. var selectText;
  292. if(attr["selectText"]) {
  293. selectText = $parse(attr["selectText"]);
  294. }
  295. return function(_scope, _element, _attrs) {
  296. var firstChild = (_attrs.focusFirstChild !== undefined);
  297. $timeout(function () {
  298. if (firstChild) {
  299. // look for first input-element in child-tree and focus that
  300. var inputs = _element.find('input');
  301. if (inputs && inputs.length > 0) {
  302. inputs[0].focus();
  303. if(selectText && selectText(_scope.$parent)) {
  304. input[0].setSelectionRange(0,input[0].value.length);
  305. }
  306. }
  307. } else {
  308. // Focus element where the directive is put on
  309. _element[0].focus();
  310. if(selectText && selectText(_scope.$parent)) {
  311. _element[0].setSelectionRange(0,_element[0].value.length);
  312. }
  313. }
  314. }, 100);
  315. }
  316. }
  317. };
  318. }]);
  319. flowableModule
  320. .directive('focusWhen', ['$timeout', function ($timeout) {
  321. return {
  322. link: function (scope, element, attrs) {
  323. scope.$watch(attrs.ngFocus, function (val) {
  324. if (angular.isDefined(val) && val) {
  325. $timeout(function () {
  326. element[0].focus();
  327. });
  328. }
  329. }, true);
  330. element.bind('blur', function () {
  331. if (angular.isDefined(attrs.ngFocusLost)) {
  332. scope.safeApply(attrs.ngFocusLost);
  333. }
  334. });
  335. }
  336. };
  337. }]);
  338. flowableModule
  339. .directive('loading', [function() {
  340. var directive = {};
  341. directive.restrict = 'A';
  342. directive.template = '<div class="loading" ng-show="loading"><div class="l1"></div><div class="l2"></div><div class="l3"></div></div>';
  343. directive.scope = {
  344. loading : "=loading",
  345. loadingText: "=loadingText"
  346. };
  347. return directive;
  348. }]);
  349. // Workaround for https://github.com/twbs/bootstrap/issues/8379 :
  350. // prototype.js interferes with regular dropdown behavior
  351. flowableModule
  352. .directive('activitiFixDropdownBug', function() {
  353. return {
  354. restrict: 'AEC',
  355. link: function(scope, element, attrs) {
  356. element.on('hidden.bs.dropdown ', function () {
  357. element.show(); // evil prototype.js has added display:none to it ...
  358. })
  359. }
  360. };
  361. });
  362. /**
  363. * Directive for rendering user link.
  364. */
  365. flowableModule
  366. .directive('userLink', function() {
  367. var directive = {};
  368. directive.template = '{{user.firstName && user.firstName || ""}} {{user.lastName && user.lastName || ""}} {{ (user.email && !user.firstName && !user.lastName) && user.email || ""}}';
  369. directive.scope = {
  370. user: "=userLink"
  371. };
  372. directive.compile = function(element, attributes) {
  373. element.addClass('people-link');
  374. };
  375. return directive;
  376. });
  377. /**
  378. * Directive for rendering a form field.
  379. */
  380. flowableModule
  381. .directive('formField', function () {
  382. var directive = {};
  383. directive.template = ' {{field.name || ""}} - {{field.id}}';
  384. directive.scope = {
  385. field: "=formField"
  386. };
  387. directive.compile = function (element, attributes) {
  388. element.addClass('form-field');
  389. };
  390. return directive;
  391. });
  392. /**
  393. * Directive to capture mouse up, down, enter and escape on input fields (eg. list navigation)
  394. */
  395. flowableModule
  396. .directive('customKeys', ["$parse", function ($parse) {
  397. var directive = {};
  398. directive.compile = function($element, attr) {
  399. var up, down, enter, escape;
  400. if(attr["upPressed"]) {
  401. up = $parse(attr["upPressed"]);
  402. }
  403. if(attr["downPressed"]) {
  404. down = $parse(attr["downPressed"]);
  405. }
  406. if(attr["enterPressed"]) {
  407. enter = $parse(attr["enterPressed"]);
  408. }
  409. if(attr["escapePressed"]) {
  410. escape = $parse(attr["escapePressed"]);
  411. }
  412. return function(scope, element, attr) {
  413. element.on('keyup', function(e) {
  414. if(e.keyCode === 38) {
  415. scope.$apply(function() {
  416. if(up) {
  417. up(scope, {$event:e});
  418. }
  419. });
  420. } else if(e.keyCode === 40) {
  421. scope.$apply(function() {
  422. if(down) {
  423. down(scope, {$event:e});
  424. }
  425. });
  426. } else if(e.keyCode === 13) {
  427. scope.$apply(function() {
  428. if(enter) {
  429. enter(scope, {$event:e});
  430. }
  431. });
  432. } else if(e.keyCode === 27) {
  433. scope.$apply(function() {
  434. if(escape) {
  435. escape(scope, {$event:e});
  436. }
  437. });
  438. }
  439. });
  440. element.on('keydown', element, function (e) {
  441. if (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 13 || e.keyCode === 27)
  442. e.preventDefault();
  443. });
  444. };
  445. };
  446. return directive;
  447. }]);
  448. // Delayed setting of model value in scope, based on input value unchanged after a number of millis
  449. // See below: ngDebounce is preferred (as it hooks into ngModel, meaning that ng-change will keep working - but not with delayedModel)
  450. flowableModule
  451. .directive('delayedModel', ['$timeout', function($timeout) {
  452. return {
  453. scope: {
  454. targetModel: '=delayedModel'
  455. },
  456. link: function(scope, element, attrs) {
  457. element.val(scope.targetModel);
  458. // Also watch model for any changes not triggered by timer
  459. scope.$watch('targetModel', function(newVal, oldVal) {
  460. if(scheduled) {
  461. $timeout.cancel(scheduled);
  462. }
  463. if (newVal !== oldVal) {
  464. element.val(scope.targetModel);
  465. }
  466. });
  467. var scheduled;
  468. element.on('keyup paste search', function() {
  469. if(element.val() !== scope.targetModel) {
  470. if(scheduled) {
  471. $timeout.cancel(scheduled);
  472. }
  473. scheduled = $timeout(function() {
  474. scope.targetModel = element[0].value;
  475. element.val(scope.targetModel);
  476. scope.$apply();
  477. }, attrs.delay || 200);
  478. }
  479. });
  480. }
  481. };
  482. }]);
  483. // From https://gist.github.com/benbrandt22/bb44184a2eddcd4b0b8a
  484. flowableModule.directive('ngDebounce', ['$timeout', function($timeout) {
  485. return {
  486. restrict: 'A',
  487. require: 'ngModel',
  488. priority: 99,
  489. link: function(scope, elm, attr, ngModelCtrl) {
  490. if (attr.type === 'radio' || attr.type === 'checkbox') return;
  491. elm.unbind('input');
  492. var debounce;
  493. elm.bind('input', function() {
  494. $timeout.cancel(debounce);
  495. debounce = $timeout( function() {
  496. scope.$apply(function() {
  497. ngModelCtrl.$setViewValue(elm.val());
  498. });
  499. }, attr.ngDebounce || 1000);
  500. });
  501. elm.bind('blur', function() {
  502. scope.$apply(function() {
  503. ngModelCtrl.$setViewValue(elm.val());
  504. });
  505. });
  506. }
  507. }
  508. }]);
  509. flowableModule.directive('externalContent', ['$parse', '$timeout', 'appResourceRoot', function ($parse, $timeout, appResourceRoot) {
  510. var directive = {};
  511. directive.restrict = 'A';
  512. directive.templateUrl = appResourceRoot + '../views/common/templates/external-content-template.html';
  513. directive.scope = {
  514. taskId : '=taskId',
  515. processInstanceId: '=formDefinition',
  516. caseInstanceId: '=caseId',
  517. folderSelect: '=folderSelect',
  518. linkOnly: '=linkOnly',
  519. preSelectedAlfrescoAccount: '=account',
  520. uploadInProgress: '=uploadInProgress'
  521. };
  522. directive.link = function ($scope, $element, $attributes) {
  523. if($attributes["onContentUpload"]) {
  524. $scope.uploadedCallback = $parse($attributes['onContentUpload']);
  525. }
  526. if($attributes["onFolderSelect"]) {
  527. $scope.folderSelectCallback = $parse($attributes['onFolderSelect']);
  528. }
  529. if($attributes["onUploadInProgress"]) {
  530. $scope.uploadInProgressCallback = $parse($attributes['onUploadInProgress']);
  531. }
  532. // Schedule hooking in of click-listener. we cannot use angular ng-click since this
  533. // will force a new $apply/$digest to happen when the click() is called on the button
  534. $timeout(function() {
  535. $element.find('div.select-file').click(function() {
  536. $element.find('input[type="file"]').click();
  537. });
  538. }, 200);
  539. };
  540. directive.controller = ['$scope', '$element', 'RelatedContentService', '$modal', '$window', '$translate', '$rootScope', function($scope, $element, RelatedContentService, $modal, $window, $translate, $rootScope) {
  541. $scope.uploadModel = {uploading: false};
  542. $scope.clearPopupError = function() {
  543. };
  544. $scope.openFileSelect = function(element) {
  545. $scope.errorMessage = undefined;
  546. var parent = angular.element(element).parent();
  547. parent.children('input').click();
  548. };
  549. $scope.onFileSelect = function ($files, isIE) {
  550. $scope.errorMessage = undefined;
  551. if(!$scope.linkOnly) {
  552. $scope.errorMessage = undefined;
  553. if(!$scope.folderSelect && !$scope.uploadModel.uploading && $files.length > 0) {
  554. if ($scope.uploadInProgressCallback) {
  555. $scope.uploadInProgressCallback($scope.$parent, {status: true});
  556. }
  557. $scope.uploadModel.uploading = true;
  558. var file = $files[0];
  559. $scope.clearPopupError();
  560. RelatedContentService.addRelatedContent($scope.taskId, $scope.processInstanceId, $scope.caseInstanceId, file, isIE).progress(function (evt) {
  561. $scope.uploadModel.uploadProgress = parseInt(100.0 * evt.loaded / evt.total);
  562. }).then(function (data) {
  563. if ($scope.uploadInProgressCallback) {
  564. $scope.uploadInProgressCallback($scope.$parent, {status: false});
  565. }
  566. $scope.uploadModel.uploading = false;
  567. if($scope.uploadedCallback) {
  568. $scope.uploadedCallback($scope.$parent, {'content': data});
  569. }
  570. }, function (error) {
  571. // Error callback
  572. if ($scope.uploadInProgressCallback) {
  573. $scope.uploadInProgressCallback($scope.$parent, {status: false});
  574. }
  575. if(error && error.messageKey) {
  576. var formattedData = {};
  577. if(error.customData) {
  578. formattedData.quota = $scope.formatFileSize(error.customData.quota)
  579. };
  580. $translate(error.messageKey, formattedData).then(function(message) {
  581. $scope.errorMessage = message;
  582. });
  583. }
  584. $scope.uploadModel.uploading = false;
  585. });
  586. }
  587. }
  588. };
  589. }];
  590. return directive;
  591. }]);
  592. flowableModule.
  593. directive('selectPeoplePopover', ['$rootScope', '$http', '$popover', 'appResourceRoot', 'UserService', '$parse', function($rootScope, $http, $popover, appResourceRoot, UserService, $parse) {
  594. var directive = {};
  595. directive.restrict = 'A';
  596. directive.scope = {
  597. excludeTaskId: '=excludeTaskId',
  598. excludeProcessId: '=excludeProcessId',
  599. excludeUserId: '=excludeUserId',
  600. excludeUserIds: '=excludeUserIds',
  601. tenantId: '=tenantId',
  602. type: '=type',
  603. restrictWithGroup: '=restrictWithGroup',
  604. selectPeopleFormFields: '=selectPeopleFormFields',
  605. ignoreContainer: '=ignoreContainer'
  606. };
  607. directive.link = function($scope, $element, attrs) {
  608. // Set defaults
  609. var placement = "bottom";
  610. $element.addClass("toggle-people-select");
  611. if(attrs.placement) {
  612. placement = attrs.placement;
  613. }
  614. var closeOnSelect = true;
  615. if(attrs.closeOnSelect !== undefined) {
  616. closeOnSelect = attrs.closeOnSelect;
  617. }
  618. if ($scope.ignoreContainer) {
  619. $scope.popover = $popover($element, {template: appResourceRoot + '../views/common/popover/select-people-popover.html?' +
  620. Date.now(), placement: placement});
  621. } else {
  622. $scope.popover = $popover($element, {template: appResourceRoot + '../views/common/popover/select-people-popover.html?' +
  623. Date.now(), placement: placement, container: 'body'});
  624. }
  625. // Parse callbacks
  626. var selectedCallback, cancelledCallback;
  627. if (attrs['onPeopleSelected']) {
  628. selectedCallback = $parse(attrs['onPeopleSelected']);
  629. }
  630. if (attrs['onCancel']) {
  631. cancelledCallback = $parse(attrs['onCancel']);
  632. }
  633. // Parse type
  634. // Can be 'workflow' or 'idm'. In 'workflow', the users are retrieved for filling task assignments etc. This is the default if this param is omitted. 'idm' is more strict.
  635. var backendType = 'workflow';
  636. if ($scope.type !== null && $scope.type !== undefined) {
  637. backendType = $scope.type;
  638. }
  639. var popoverScope = $scope.popover.$scope;
  640. popoverScope.title = attrs['popoverTitle'];
  641. popoverScope.popupModel = {
  642. userResults: [],
  643. userField: {},
  644. userFieldFilter: ['people']
  645. };
  646. if ($scope.selectPeopleFormFields) {
  647. popoverScope.popupModel.formFields = $scope.selectPeopleFormFields;
  648. }
  649. popoverScope.setSearchType = function() {
  650. popoverScope.popupModel.userSourceType = 'search';
  651. };
  652. popoverScope.setFormFieldType = function() {
  653. popoverScope.popupModel.userSourceType = 'field';
  654. };
  655. popoverScope.$watch('popupModel.userField', function() {
  656. if (popoverScope.popupModel.userField && popoverScope.popupModel.userField.id) {
  657. if (selectedCallback) {
  658. // Run callback in parent scope of directive
  659. var simpleUserField = {
  660. id: popoverScope.popupModel.userField.id,
  661. name: popoverScope.popupModel.userField.name,
  662. type: popoverScope.popupModel.userField.type
  663. }
  664. selectedCallback($scope.$parent, {'userField': simpleUserField});
  665. popoverScope.popupModel.userField = {};
  666. }
  667. if (closeOnSelect || closeOnSelect === 'true') {
  668. popoverScope.$hide();
  669. }
  670. }
  671. });
  672. popoverScope.$watch('popupModel.filter', function() {
  673. if (popoverScope.popupModel.filter && popoverScope.popupModel.filter.length > 0) {
  674. var userGetPromise;
  675. if (backendType === 'idm') {
  676. userGetPromise = UserService.getFilteredUsersStrict(popoverScope.popupModel.filter, $scope.tenantId, $scope.restrictWithGroup);
  677. } else {
  678. // Default: go to workflow users backend
  679. userGetPromise = UserService.getFilteredUsers(popoverScope.popupModel.filter, $scope.excludeTaskId,
  680. $scope.excludeProcessId, $scope.tenantId, $scope.restrictWithGroup);
  681. }
  682. userGetPromise.then(function(result) {
  683. popoverScope.popupModel.showRecentResults = false;
  684. var users = [];
  685. var excludeUserIdSet = $scope.excludeUserId !== null && $scope.excludeUserId !== undefined;
  686. var excludeUserIdsSet = $scope.excludeUserIds !== null && $scope.excludeUserIds !== undefined;
  687. if (excludeUserIdSet === true || excludeUserIdsSet === true) {
  688. for (var userIndex=0; userIndex < result.data.length; userIndex++) {
  689. var userExcluded = false;
  690. if (excludeUserIdSet === true && result.data[userIndex].id === $scope.excludeUserId) {
  691. userExcluded = true;
  692. }
  693. if (excludeUserIdsSet === true && $scope.excludeUserIds.indexOf(result.data[userIndex].id) >= 0) {
  694. userExcluded = true;
  695. }
  696. if (!userExcluded) {
  697. users.push(result.data[userIndex]);
  698. }
  699. }
  700. } else {
  701. users = result.data;
  702. }
  703. popoverScope.popupModel.userResults = users;
  704. popoverScope.resetSelection();
  705. });
  706. } else {
  707. popoverScope.resetSelection();
  708. popoverScope.popupModel.userResults = [];
  709. }
  710. });
  711. popoverScope.resetSelection = function() {
  712. popoverScope.popupModel.selectedUser = undefined;
  713. popoverScope.popupModel.selectedIndex = -1;
  714. };
  715. popoverScope.nextUser = function() {
  716. var users = popoverScope.popupModel.userResults;
  717. if(users && users.length > 0 && popoverScope.popupModel.selectedIndex < users.length -1) {
  718. popoverScope.popupModel.selectedIndex+=1;
  719. popoverScope.popupModel.selectedUser = users[popoverScope.popupModel.selectedIndex];
  720. }
  721. };
  722. popoverScope.previousUser = function() {
  723. var users = popoverScope.popupModel.userResults;
  724. if(users && users.length > 0 && popoverScope.popupModel.selectedIndex > 0) {
  725. popoverScope.popupModel.selectedIndex-=1;
  726. popoverScope.popupModel.selectedUser = users[popoverScope.popupModel.selectedIndex];
  727. }
  728. };
  729. popoverScope.confirmUser = function(user) {
  730. if (!user) {
  731. // Selection is done with keyboard, use selection index
  732. var users = popoverScope.popupModel.userResults;
  733. if (popoverScope.popupModel.selectedIndex >= 0 && popoverScope.popupModel.selectedIndex <users.length) {
  734. user = users[popoverScope.popupModel.selectedIndex];
  735. }
  736. }
  737. if (user) {
  738. if (selectedCallback) {
  739. // Run callback in parent scope of directive
  740. selectedCallback($scope.$parent, {'user': user});
  741. }
  742. if (closeOnSelect === 'true') {
  743. popoverScope.$hide();
  744. } else {
  745. var users = popoverScope.popupModel.userResults;
  746. users.splice(jQuery.inArray(user, users),1);
  747. popoverScope.popupModel.selectedIndex=0;
  748. popoverScope.popupModel.selectedUser = users[popoverScope.popupModel.selectedIndex];
  749. }
  750. }
  751. };
  752. popoverScope.$on('tooltip.hide', function() {
  753. // Invalidate recent results
  754. if(popoverScope.popupModel.showRecentResults && popoverScope.popupModel.added) {
  755. popoverScope.popupModel.recentUsers = [];
  756. }
  757. popoverScope.popupModel.userResults = [];
  758. popoverScope.popupModel.filter = '';
  759. if(popoverScope.popupModel.added) {
  760. popoverScope.popupModel.added = false;
  761. } else {
  762. if(cancelledCallback) {
  763. // Run callback in parent scope of directive
  764. cancelledCallback($scope.$parent);
  765. }
  766. }
  767. });
  768. };
  769. return directive;
  770. }]);
  771. flowableModule.
  772. directive('selectFunctionalGroupPopover', ['$rootScope', '$http', '$popover','appResourceRoot', 'FunctionalGroupService', '$parse',
  773. function($rootScope, $http, $popover, appResourceRoot, FunctionalGroupService, $parse) {
  774. var directive = {};
  775. directive.restrict = 'A';
  776. directive.scope = {
  777. type: '=type',
  778. ignoreContainer: '=ignoreContainer',
  779. restrictWithGroup: '=restrictWithGroup',
  780. excludeGroupIds: '=excludeGroupIds'
  781. };
  782. directive.link = function($scope, $element, attrs) {
  783. // Set defaults
  784. var placement = "bottom";
  785. $element.addClass("toggle-functional-group-select");
  786. if (attrs.placement) {
  787. placement = attrs.placement;
  788. }
  789. var closeOnSelect = true;
  790. if (attrs.closeOnSelect !== undefined) {
  791. closeOnSelect = attrs.closeOnSelect;
  792. }
  793. if ($scope.ignoreContainer) {
  794. $scope.popover = $popover($element, {template: appResourceRoot + '../views/common/popover/select-functional-group-popover.html?' +
  795. Date.now(), placement: placement});
  796. } else {
  797. $scope.popover = $popover($element, {template: appResourceRoot + '../views/common/popover/select-functional-group-popover.html?' +
  798. Date.now(), placement: placement, container: 'body'});
  799. }
  800. // Parse callbacks
  801. var selectedCallback, cancelledCallback;
  802. if (attrs['onGroupSelected']) {
  803. selectedCallback = $parse(attrs['onGroupSelected']);
  804. }
  805. if (attrs['onCancel']) {
  806. cancelledCallback = $parse(attrs['onCancel']);
  807. }
  808. var popoverScope = $scope.popover.$scope;
  809. popoverScope.title = attrs['popoverTitle'];
  810. popoverScope.popupModel = {
  811. groupResults: []
  812. };
  813. popoverScope.$watch('popupModel.filter', function() {
  814. if (popoverScope.popupModel.filter && popoverScope.popupModel.filter.length > 0) {
  815. var tenantId;
  816. if ($rootScope.common !== null && $rootScope.common !== undefined && $rootScope.common.selectedTenantId !== null && $rootScope.common.selectedTenantId !== undefined) {
  817. tenantId = $rootScope.common.selectedTenantId > 0 ? $rootScope.common.selectedTenantId : undefined;
  818. }
  819. FunctionalGroupService.getFilteredGroups(popoverScope.popupModel.filter, $scope.restrictWithGroup, tenantId).then(function(result) {
  820. var groups = [];
  821. if ($scope.excludeGroupId != null && $scope.excludeGroupId) {
  822. for (var groupIndex=0; groupIndex < result.data.length; groupIndex++) {
  823. if (result.data[groupIndex].id !== $scope.excludeGroupId) {
  824. groups.push(result.data[groupIndex]);
  825. }
  826. }
  827. } else if ($scope.excludeGroupIds != null && $scope.excludeGroupIds !== undefined) {
  828. for (var groupIndex=0; groupIndex < result.data.length; groupIndex++) {
  829. if ($scope.excludeGroupIds.indexOf(result.data[groupIndex].id) < 0) {
  830. groups.push(result.data[groupIndex]);
  831. }
  832. }
  833. } else {
  834. groups = result.data;
  835. }
  836. popoverScope.popupModel.groupResults = groups;
  837. popoverScope.resetSelection();
  838. });
  839. } else {
  840. popoverScope.resetSelection();
  841. popoverScope.popupModel.groupResults = [];
  842. }
  843. });
  844. popoverScope.resetSelection = function() {
  845. popoverScope.popupModel.selectedGroup = undefined;
  846. popoverScope.popupModel.selectedIndex = -1;
  847. };
  848. popoverScope.nextGroup = function() {
  849. var groups = popoverScope.popupModel.groupResults;
  850. if (groups && groups.length > 0 && popoverScope.popupModel.selectedIndex < groups.length -1) {
  851. popoverScope.popupModel.selectedIndex+=1;
  852. popoverScope.popupModel.groupUser = groups[popoverScope.popupModel.selectedIndex];
  853. }
  854. };
  855. popoverScope.previousGroup = function() {
  856. var groups = popoverScope.popupModel.groupResults;
  857. if (groups && groups.length > 0 && popoverScope.popupModel.selectedIndex > 0) {
  858. popoverScope.popupModel.selectedIndex-=1;
  859. popoverScope.popupModel.selectedGroup = groups[popoverScope.popupModel.selectedIndex];
  860. }
  861. };
  862. popoverScope.confirmGroup = function(group) {
  863. if (!group) {
  864. // Selection is done with keyboard, use selection index
  865. var groups = popoverScope.popupModel.groupResults;
  866. if (popoverScope.popupModel.selectedIndex >= 0 && popoverScope.popupModel.selectedIndex < groups.length) {
  867. group = groups[popoverScope.popupModel.selectedIndex];
  868. }
  869. }
  870. if (group) {
  871. if(selectedCallback) {
  872. // Run callback in parent scope of directive
  873. selectedCallback($scope.$parent, {'group': group});
  874. }
  875. if (closeOnSelect === 'true') {
  876. popoverScope.$hide();
  877. } else {
  878. var groups = popoverScope.popupModel.groupResults;
  879. groups.splice(jQuery.inArray(group, groups), 1);
  880. popoverScope.popupModel.selectedIndex = 0;
  881. popoverScope.popupModel.selectedGroup = groups[popoverScope.popupModel.selectedIndex];
  882. }
  883. }
  884. };
  885. popoverScope.$on('tooltip.hide', function() {
  886. popoverScope.popupModel.groupResults = [];
  887. popoverScope.popupModel.filter = '';
  888. if (popoverScope.popupModel.added) {
  889. popoverScope.popupModel.added = false;
  890. } else {
  891. if (cancelledCallback) {
  892. // Run callback in parent scope of directive
  893. cancelledCallback($scope.$parent);
  894. }
  895. }
  896. });
  897. };
  898. return directive;
  899. }]);
  900. flowableModule.directive('tabControl', ['$compile', '$http', '$templateCache', function($compile, $http, $templateCache) {
  901. var updateTemplate = function($scope, element, attributes) {
  902. if(!$scope.activeTemplate || $scope.activeTemplate != $scope.activeTab.id) {
  903. // Check if current loaded template is still the right one
  904. var contentDiv = $(element.children()[1]);
  905. var childScope = angular.element(element.children()[1]).scope();
  906. if($scope.activeTemplate && childScope != $scope) {
  907. // Child-scope created by the included element, should be destroyed
  908. childScope.$destroy();
  909. }
  910. if($scope.activeTab && $scope.activeTab.templateUrl) {
  911. // Load the HTML-fragment or get from cache
  912. var loader = $http.get($scope.activeTab.templateUrl, {cache: $templateCache});
  913. var promise = loader.success(function(html) {
  914. contentDiv.html(html);
  915. }).then(function (response) {
  916. $scope.activeTemplate = $scope.activeTab.id;
  917. contentDiv.replaceWith($compile(contentDiv.html())($scope));
  918. });
  919. } else {
  920. // No templates are being used, no need to use the contentDiv for this tab, clear it
  921. contentDiv.empty();
  922. }
  923. }
  924. };
  925. var directive = {};
  926. directive.restrict = 'A';
  927. directive.replace = true;
  928. directive.transclude = true;
  929. directive.template = '<div><div class="clearfix"><ul class="tabs clearfix">' +
  930. '<li ng-repeat="tab in tabs" ng-class="{\'active\': tab.id == activeTab.id}"><a ng-click="tabClicked(tab)">{{tab.title && (tab.title | translate) || (tab.name | translate)}}</a></li>' +
  931. '</ul></div>' +
  932. '<div></div>' +
  933. '</div>';
  934. directive.scope = {
  935. possibleTabs : "=tabControl",
  936. model: "=model",
  937. activeTabReference: "=activeTab"
  938. };
  939. directive.controller = ['$scope', '$element', function($scope, $element) {
  940. $scope.refreshTabs = function() {
  941. var tabs = [];
  942. for(var i=0; i < $scope.possibleTabs.length; i++) {
  943. var tab = $scope.possibleTabs[i];
  944. if(!tab.hide) {
  945. tabs.push(tab);
  946. }
  947. }
  948. $scope.tabs = tabs;
  949. };
  950. $scope.$watch('possibleTabs', function() {
  951. $scope.refreshTabs();
  952. }, true);
  953. $scope.$watch('activeTabReference', function(newValue, oldValue) {
  954. if(!$scope.activeTab || $scope.activeTab.id != newValue) {
  955. // Active tab ID changed from outside of the directive controller, need to switch to the
  956. // right tab within this scope
  957. var newTab = $scope.findTab(newValue);
  958. if(newTab) {
  959. $scope.tabClicked(newTab);
  960. }
  961. }
  962. });
  963. $scope.findTab = function(tabId) {
  964. if($scope.possibleTabs) {
  965. for(var i=0; i< $scope.possibleTabs.length; i++) {
  966. if($scope.possibleTabs[i].id == tabId) {
  967. return $scope.possibleTabs[i];
  968. }
  969. }
  970. }
  971. return undefined;
  972. };
  973. $scope.tabClicked = function(tab) {
  974. if (tab.hide) {
  975. tab.hide = false;
  976. $scope.refreshTabs();
  977. }
  978. $scope.activeTab = tab;
  979. if (tab) {
  980. $scope.activeTabReference = tab.id;
  981. } else {
  982. $scope.activeTabReference = undefined;
  983. }
  984. updateTemplate($scope, $element);
  985. };
  986. $scope.refreshTabs();
  987. if($scope.tabs && $scope.tabs.length > 0) {
  988. if($scope.activeTabReference) {
  989. $scope.activeTab = $scope.findTab($scope.activeTabReference);
  990. }
  991. if(!$scope.activeTab) {
  992. // Revert to the first tab, if no tab is forced to be shown first
  993. $scope.activeTab = $scope.tabs[0];
  994. }
  995. $scope.tabClicked($scope.activeTab);
  996. }
  997. }];
  998. directive.link = updateTemplate;
  999. return directive;
  1000. }]);
  1001. /**
  1002. * Directive that calls the function present in the toggle-dragover attribute with a single parameter (over) when
  1003. * dragging over the element has started (over = true) or ended (over = false)
  1004. */
  1005. flowableModule
  1006. .directive('toggleDragover', ["$document", "$parse", function ($document, $parse) {
  1007. var linkFunction = function ($scope, $element, $attributes) {
  1008. var toggleFunction = $attributes.toggleDragover;
  1009. var callback = $parse(toggleFunction);
  1010. var el = $element[0];
  1011. el.addEventListener('dragenter',function(e) {
  1012. $scope.$apply(function() {
  1013. callback($scope, {'over': true});
  1014. });
  1015. return false;
  1016. },
  1017. false
  1018. );
  1019. el.addEventListener('dragleave', function(e) {
  1020. $scope.$apply(function() {
  1021. callback($scope, {'over': false});
  1022. });
  1023. return false;
  1024. },
  1025. false
  1026. );
  1027. };
  1028. return( linkFunction );
  1029. }]);
  1030. flowableModule.directive('editInPlace', function () {
  1031. return {
  1032. restrict: 'E',
  1033. scope: {
  1034. value: '='
  1035. },
  1036. template: '<span ng-click="edit()" ng-bind="value"></span><span class="glyphicon glyphicon-pencil edit-in-place-icon"></span><input ng-model="value" class="inline-edit-value form-control" ng-blur="stopEdit()" custom-keys enter-pressed="stopEdit()">',
  1037. link: function ($scope, element, attrs) {
  1038. var iconElement = angular.element(element.children()[1]);
  1039. var inputElement = angular.element(element.children()[2]);
  1040. // This directive should have a set class so we can style it.
  1041. element.addClass('edit-in-place');
  1042. // Initially, we're not editing.
  1043. $scope.editing = false;
  1044. // ng-click handler to activate edit-in-place
  1045. $scope.edit = function () {
  1046. $scope.editing = true;
  1047. // We control display through a class on the directive itself. See the CSS.
  1048. element.addClass('active');
  1049. // And we must focus the element.
  1050. // `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
  1051. // we have to reference the first element in the array.
  1052. inputElement[0].focus();
  1053. };
  1054. $scope.stopEdit = function() {
  1055. $scope.editing = false;
  1056. element.removeClass('active');
  1057. };
  1058. // // When we leave the input, we're done editing.
  1059. // inputElement.prop('onblur', function () {
  1060. // console.log('ONBLUR');
  1061. // $scope.editing = false;
  1062. // element.removeClass('active');
  1063. // });
  1064. }
  1065. };
  1066. });
  1067. /* UTILITY METHODS */
  1068. /**
  1069. * This creates a modal window that auto closes on route change.
  1070. * By default, this is NOT the case, and leads to some funny behaviour.
  1071. *
  1072. * Use this method vs the default $modal({myJson}) approach
  1073. */
  1074. var _internalCreateModal = function(modalConfig, $modal, $scope) {
  1075. if ($scope !== null && $scope !== undefined) {
  1076. $scope.modal = $modal(modalConfig);
  1077. $scope.$on('$routeChangeStart', function () {
  1078. if ($scope.modal) {
  1079. $scope.modal.hide();
  1080. }
  1081. });
  1082. return $scope.modal;
  1083. } else {
  1084. return $modal(modalConfig);
  1085. }
  1086. };
  1087. flowableModule.
  1088. directive('numberInputCheck', function() {
  1089. return {
  1090. require: 'ngModel',
  1091. link: function(scope, element, attrs, modelCtrl) {
  1092. modelCtrl.$parsers.push(function (inputValue) {
  1093. var transformedInput;
  1094. if (inputValue && inputValue.indexOf('-') == 0) {
  1095. transformedInput = inputValue.substr(1).replace(/([^0-9])/g, '');
  1096. transformedInput = '-' + transformedInput;
  1097. } else {
  1098. transformedInput = inputValue.replace(/([^0-9])/g, '');
  1099. }
  1100. if (transformedInput != inputValue) {
  1101. modelCtrl.$setViewValue(transformedInput);
  1102. modelCtrl.$render();
  1103. }
  1104. return transformedInput;
  1105. });
  1106. }
  1107. };
  1108. });
  1109. flowableModule.
  1110. directive('decimalNumberInputCheck', function() {
  1111. return {
  1112. require: 'ngModel',
  1113. link: function(scope, element, attrs, modelCtrl) {
  1114. modelCtrl.$parsers.push(function (inputValue) {
  1115. var transformedInput = inputValue;
  1116. var negativeSign = '';
  1117. if (transformedInput && transformedInput.indexOf('-') == 0) {
  1118. negativeSign = '-';
  1119. transformedInput = inputValue.substr(1);
  1120. }
  1121. if(transformedInput && transformedInput.indexOf('.') == 0 ){
  1122. transformedInput = "0" + transformedInput;
  1123. }
  1124. if(transformedInput && transformedInput.indexOf('.') > -1){
  1125. var dotIndex = transformedInput.indexOf('.');
  1126. var left = transformedInput.substr(0, dotIndex);
  1127. var right = transformedInput.substr(dotIndex+1);
  1128. left = left.replace(/([^0-9])/g, '');
  1129. right = right.replace(/([^0-9])/g, '');
  1130. transformedInput = negativeSign + left + '.' + right;
  1131. }
  1132. else{
  1133. transformedInput = negativeSign + transformedInput.replace(/([^0-9])/g, '');
  1134. }
  1135. if (transformedInput != inputValue) {
  1136. modelCtrl.$setViewValue(transformedInput);
  1137. modelCtrl.$render();
  1138. }
  1139. return transformedInput;
  1140. });
  1141. }
  1142. };
  1143. });