forms-angular 0.12.0-beta.193 → 0.12.0-beta.194
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/forms-angular.js +661 -199
- package/dist/client/forms-angular.min.js +1 -1
- package/dist/client/index.d.ts +98 -8
- package/dist/server/data_form.js +714 -539
- package/dist/server/index.d.ts +19 -3
- package/package.json +11 -11
|
@@ -4,38 +4,44 @@ var fng;
|
|
|
4
4
|
var directives;
|
|
5
5
|
(function (directives) {
|
|
6
6
|
/*@ngInject*/
|
|
7
|
-
|
|
7
|
+
modelControllerDropdown.$inject = ["securityService"];
|
|
8
|
+
function modelControllerDropdown(securityService) {
|
|
8
9
|
var menuVisibilityStr;
|
|
9
10
|
var menuDisabledStr;
|
|
10
11
|
var itemVisibilityStr = "isHidden($index)";
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// class="{{ }}". note that we "prevent" a disabled menu item from being clicked by checking for the DISABLED
|
|
19
|
-
// attribute in the doClick(...) function, and aborting if this is found.
|
|
20
|
-
// note that the 'normal' class introduced here might not actually do anything, but for one-time binding to work
|
|
21
|
-
// properly, we need a truthy value
|
|
22
|
-
itemClassStr += " class=\"{{ ".concat(bindingStr, "isSecurelyDisabled(choice.id) ? 'disabled' : 'normal' }}\"");
|
|
12
|
+
var itemDisabledStr = "ng-class=\"dropdownClass($index)\"";
|
|
13
|
+
// without a more fundamental re-write, we cannot support "instant" binding here, so we'll fall-back to using
|
|
14
|
+
// the next-best alternative, which is one-time binding
|
|
15
|
+
var oneTimeBinding = fng.formsAngular.elemSecurityFuncBinding !== "normal";
|
|
16
|
+
var bindingStr = oneTimeBinding ? "::" : "";
|
|
17
|
+
if (securityService.canDoSecurity("hidden")) {
|
|
18
|
+
menuVisibilityStr = "ng-if=\"contextMenuId && !contextMenuHidden\" ".concat(securityService.getHideableAttrs('{{ ::contextMenuId }}'));
|
|
23
19
|
if (oneTimeBinding) {
|
|
24
20
|
// because the isHidden(...) logic is highly likely to be model dependent, that cannot be one-time bound. to
|
|
25
21
|
// be able to combine one-time and regular binding, we'll use ng-if for the one-time bound stuff and ng-hide for the rest
|
|
26
|
-
itemVisibilityStr = "ng-if=\"
|
|
22
|
+
itemVisibilityStr = "ng-if=\"::(choice.divider || !isSecurelyHidden(choice.id))\" ng-hide=\"".concat(itemVisibilityStr, "\"");
|
|
27
23
|
}
|
|
28
24
|
else if (fng.formsAngular.elemSecurityFuncBinding === "normal") {
|
|
29
|
-
itemVisibilityStr = "ng-hide=\"
|
|
25
|
+
itemVisibilityStr = "ng-hide=\"".concat(itemVisibilityStr, " || (!choice.divider && isSecurelyHidden(choice.id))\"");
|
|
30
26
|
}
|
|
31
|
-
|
|
32
|
-
menuDisabledStr = "disableable-link ng-disabled=\"contextMenuDisabled\"";
|
|
27
|
+
itemVisibilityStr += " ".concat(securityService.getHideableAttrs('{{ ::choice.id }}'));
|
|
33
28
|
}
|
|
34
29
|
else {
|
|
35
30
|
menuVisibilityStr = "";
|
|
36
|
-
menuDisabledStr = "";
|
|
37
31
|
itemVisibilityStr = "ng-hide=\"".concat(itemVisibilityStr, "\"");
|
|
38
32
|
}
|
|
33
|
+
if (securityService.canDoSecurity("disabled")) {
|
|
34
|
+
menuDisabledStr = "disableable-link ng-disabled=\"contextMenuDisabled\" ".concat(securityService.getDisableableAttrs('{{ ::contextMenuId }}'));
|
|
35
|
+
// as ng-class is already being used, we'll add the .disabled class if the menu item is securely disabled using
|
|
36
|
+
// class="{{ }}". note that we "prevent" a disabled menu item from being clicked by checking for the DISABLED
|
|
37
|
+
// attribute in the doClick(...) function, and aborting if this is found.
|
|
38
|
+
// note that the 'normal' class introduced here might not actually do anything, but for one-time binding to work
|
|
39
|
+
// properly, we need a truthy value
|
|
40
|
+
itemDisabledStr += " class=\"{{ ".concat(bindingStr, "(!choice.divider && isSecurelyDisabled(choice.id)) ? 'disabled' : 'normal' }}\" ").concat(securityService.getDisableableAttrs('{{ ::choice.id }}'));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
menuDisabledStr = "";
|
|
44
|
+
}
|
|
39
45
|
return {
|
|
40
46
|
restrict: "AE",
|
|
41
47
|
replace: true,
|
|
@@ -44,7 +50,7 @@ var fng;
|
|
|
44
50
|
' {{contextMenu}} <b class="caret"></b>' +
|
|
45
51
|
" </a>" +
|
|
46
52
|
' <ul class="uib-dropdown-menu dropdown-menu">' +
|
|
47
|
-
" <li ng-repeat=\"choice in items\" ng-attr-id=\"{{choice.id}}\" ".concat(itemVisibilityStr, " ").concat(
|
|
53
|
+
" <li ng-repeat=\"choice in items\" ng-attr-id=\"{{choice.id}}\" ".concat(itemVisibilityStr, " ").concat(itemDisabledStr, ">") +
|
|
48
54
|
' <a ng-show="choice.text || choice.textFunc" class="dropdown-option" ng-href="{{choice.url || choice.urlFunc()}}" ng-click="doClick($index, $event)">' +
|
|
49
55
|
" {{ choice.text || choice.textFunc() }}" +
|
|
50
56
|
" </a>" +
|
|
@@ -267,7 +273,7 @@ var fng;
|
|
|
267
273
|
(function (fng) {
|
|
268
274
|
var directives;
|
|
269
275
|
(function (directives) {
|
|
270
|
-
formInput.$inject = ["$compile", "$rootScope", "$filter", "$timeout", "cssFrameworkService", "formGenerator", "formMarkupHelper"];
|
|
276
|
+
formInput.$inject = ["$compile", "$rootScope", "$filter", "$timeout", "cssFrameworkService", "formGenerator", "formMarkupHelper", "securityService"];
|
|
271
277
|
var tabsSetupState;
|
|
272
278
|
(function (tabsSetupState) {
|
|
273
279
|
tabsSetupState[tabsSetupState["Y"] = 0] = "Y";
|
|
@@ -275,7 +281,7 @@ var fng;
|
|
|
275
281
|
tabsSetupState[tabsSetupState["Forced"] = 2] = "Forced";
|
|
276
282
|
})(tabsSetupState || (tabsSetupState = {}));
|
|
277
283
|
/*@ngInject*/
|
|
278
|
-
function formInput($compile, $rootScope, $filter, $timeout, cssFrameworkService, formGenerator, formMarkupHelper) {
|
|
284
|
+
function formInput($compile, $rootScope, $filter, $timeout, cssFrameworkService, formGenerator, formMarkupHelper, securityService) {
|
|
279
285
|
return {
|
|
280
286
|
restrict: 'EA',
|
|
281
287
|
link: function (scope, element, attrs) {
|
|
@@ -390,7 +396,7 @@ var fng;
|
|
|
390
396
|
value = '<input placeholder="fng-select2 has been removed" readonly>';
|
|
391
397
|
}
|
|
392
398
|
else {
|
|
393
|
-
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo);
|
|
399
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
394
400
|
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
395
401
|
common += " aria-label=\"".concat(fieldInfo.label && fieldInfo.label !== "" ? fieldInfo.label : fieldInfo.name, "\" ");
|
|
396
402
|
value = '<select ' + common + 'class="' + allInputsVars.formControl.trim() + allInputsVars.compactClass + allInputsVars.sizeClassBS2 + '" ' + requiredStr + '>';
|
|
@@ -444,7 +450,8 @@ var fng;
|
|
|
444
450
|
case 'radio':
|
|
445
451
|
value = '';
|
|
446
452
|
common += requiredStr;
|
|
447
|
-
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo);
|
|
453
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
454
|
+
;
|
|
448
455
|
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
449
456
|
var separateLines = options.formstyle === 'vertical' || (options.formstyle !== 'inline' && !fieldInfo.inlineRadio);
|
|
450
457
|
if (angular.isArray(fieldInfo.options)) {
|
|
@@ -475,7 +482,8 @@ var fng;
|
|
|
475
482
|
break;
|
|
476
483
|
case 'checkbox':
|
|
477
484
|
common += requiredStr;
|
|
478
|
-
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo);
|
|
485
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
486
|
+
;
|
|
479
487
|
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
480
488
|
value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
|
|
481
489
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
@@ -484,7 +492,8 @@ var fng;
|
|
|
484
492
|
break;
|
|
485
493
|
default:
|
|
486
494
|
common += formMarkupHelper.addTextInputMarkup(allInputsVars, fieldInfo, requiredStr);
|
|
487
|
-
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo);
|
|
495
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
496
|
+
;
|
|
488
497
|
if (fieldInfo.type === 'textarea') {
|
|
489
498
|
if (fieldInfo.rows) {
|
|
490
499
|
if (fieldInfo.rows === 'auto') {
|
|
@@ -534,11 +543,12 @@ var fng;
|
|
|
534
543
|
return result;
|
|
535
544
|
};
|
|
536
545
|
var containerInstructions = function (info) {
|
|
537
|
-
var result
|
|
546
|
+
var result;
|
|
538
547
|
if (typeof info.containerType === 'function') {
|
|
539
548
|
result = info.containerType(info);
|
|
540
549
|
}
|
|
541
550
|
else {
|
|
551
|
+
result = {};
|
|
542
552
|
switch (info.containerType) {
|
|
543
553
|
case 'tab':
|
|
544
554
|
var tabNo = -1;
|
|
@@ -551,12 +561,26 @@ var fng;
|
|
|
551
561
|
if (tabNo >= 0) {
|
|
552
562
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
553
563
|
// result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"'
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
564
|
+
var idStr = "".concat(_.camelCase(info.title), "Tab");
|
|
565
|
+
var visibility = securityService.considerVisibility(idStr, scope);
|
|
566
|
+
if (visibility.omit) {
|
|
567
|
+
// we already know this field should be invisible, so we needn't add anything for it
|
|
568
|
+
result.omit = true;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
var attrs_1 = "id=\"".concat(idStr, "\"");
|
|
572
|
+
if (visibility.visibilityAttr) {
|
|
573
|
+
// an angular expression to determine the visibility of this field later...
|
|
574
|
+
attrs_1 += " ".concat(visibility.visibilityAttr);
|
|
575
|
+
}
|
|
576
|
+
attrs_1 += securityService.generateDisabledAttr(idStr, scope, { attr: "disable", attrRequiresValue: true }); // uib-tab expects 'disable="true"` rather than 'disabled="true"' or just disabled
|
|
577
|
+
result.before = "<uib-tab ".concat(attrs_1, " deselect=\"tabDeselect($event, $selectedIndex)\" select=\"updateQueryForTab('").concat(info.title, "')\" heading=\"").concat(info.title, "\"");
|
|
578
|
+
if (tabNo > 0) {
|
|
579
|
+
result.before += 'active="tabs[' + tabNo + '].active"';
|
|
580
|
+
}
|
|
581
|
+
result.before += '>';
|
|
582
|
+
result.after = '</uib-tab>';
|
|
557
583
|
}
|
|
558
|
-
result.before += '>';
|
|
559
|
-
result.after = '</uib-tab>';
|
|
560
584
|
}
|
|
561
585
|
else {
|
|
562
586
|
result.before = '<p>Error! Tab ' + info.title + ' not found in tab list</p>';
|
|
@@ -708,7 +732,10 @@ var fng;
|
|
|
708
732
|
if (info.formStyle === "inline" && info.inlineHeaders) {
|
|
709
733
|
template += generateInlineHeaders(info.schema, options, model, info.inlineHeaders === "always");
|
|
710
734
|
}
|
|
711
|
-
|
|
735
|
+
// handleReadOnlyDisabled() returns two strings - the 'disabled' attribute(s), and the 'disableable'
|
|
736
|
+
// attributes. for the purpose of deciding if / how to disable sorting if the list itself is
|
|
737
|
+
// disabled, we're only interested in the former...
|
|
738
|
+
var disableCond = formMarkupHelper.handleReadOnlyDisabled(info, scope)[0];
|
|
712
739
|
// if we already know that the field is disabled (only possible when formsAngular.elemSecurityFuncBinding === "instant")
|
|
713
740
|
// then we don't need to add the sortable attribute at all
|
|
714
741
|
// otherwise, we need to include the ng-disabled on the <ol> so this can be referenced by the ui-sortable directive
|
|
@@ -717,8 +744,12 @@ var fng;
|
|
|
717
744
|
? "".concat(disableCond, " ui-sortable=\"sortableOptions\" ng-model=\"").concat(model, "\"")
|
|
718
745
|
: "";
|
|
719
746
|
template += "<ol class=\"sub-doc\" ".concat(sortableStr, ">");
|
|
747
|
+
// if a "disabled + children" DOM effect is applied to the list, this should serve to disable all of the
|
|
748
|
+
// fields in the list sub-schema, along with the remove and add buttons for this list. the following
|
|
749
|
+
// string will be added to the list items and the add and remove buttons to identify this fact.
|
|
750
|
+
var disableableAncestorStr = formMarkupHelper.genDisableableAncestorStr(info.id);
|
|
720
751
|
template +=
|
|
721
|
-
"<li ng-form id=\"".concat(info.id, "List_{{$index}}\" name=\"form_").concat(niceName, "{{$index}}\" ") +
|
|
752
|
+
"<li ng-form id=\"".concat(info.id, "List_{{$index}}\" name=\"form_").concat(niceName, "{{$index}}\" ").concat(disableableAncestorStr) +
|
|
722
753
|
" class=\"".concat(convertFormStyleToClass(info.formStyle)) +
|
|
723
754
|
" ".concat(cssFrameworkService.framework() === 'bs2' ? 'row-fluid' : '') +
|
|
724
755
|
" ".concat(info.inlineHeaders ? 'width-controlled' : '') +
|
|
@@ -730,7 +761,10 @@ var fng;
|
|
|
730
761
|
template += '<div class="row-fluid sub-doc">';
|
|
731
762
|
}
|
|
732
763
|
if (info.noRemove !== true || info.customSubDoc) {
|
|
733
|
-
|
|
764
|
+
// we need to put disableableAncestorStr on the div rather than on the remove button because the
|
|
765
|
+
// way that is styled means that any coloured outlines that might be added when "Identify page elements" is on
|
|
766
|
+
// will be masked
|
|
767
|
+
template += " <div class=\"sub-doc-btns\" ".concat(info.noRemove !== true ? disableableAncestorStr : "", ">");
|
|
734
768
|
if (typeof info.customSubDoc == 'string') {
|
|
735
769
|
template += info.customSubDoc;
|
|
736
770
|
}
|
|
@@ -790,9 +824,10 @@ var fng;
|
|
|
790
824
|
else {
|
|
791
825
|
hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, "\"") : '';
|
|
792
826
|
indicatorShowCond = info.noAdd ? "ng-show=\"".concat(info.noAdd, " && ").concat(indicatorShowCond, "\"") : '';
|
|
793
|
-
|
|
827
|
+
// we need the button to have disableCond (to actually disable it, if the list is disabled)
|
|
828
|
+
// adding disableableAncestorStr seems more correct than for it to have the disableable attribute
|
|
794
829
|
footer +=
|
|
795
|
-
"<button ".concat(hideCond, " ").concat(
|
|
830
|
+
"<button ".concat(hideCond, " ").concat(disableCond, " ").concat(disableableAncestorStr, " id=\"add_").concat(info.id, "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('").concat(info.name, "',$event)\">") +
|
|
796
831
|
" <i class=\"".concat(formMarkupHelper.glyphClass(), "-plus\"></i> Add ") +
|
|
797
832
|
"</button>";
|
|
798
833
|
}
|
|
@@ -833,7 +868,7 @@ var fng;
|
|
|
833
868
|
delete info.help;
|
|
834
869
|
var inputHtml = generateInput(info, info.type === 'link' ? null : 'arrayItem.x', true, options);
|
|
835
870
|
info.help = stashedHelp;
|
|
836
|
-
template += formMarkupHelper.handleArrayInputAndControlDiv(inputHtml, controlDivClasses, info, options);
|
|
871
|
+
template += formMarkupHelper.handleArrayInputAndControlDiv(inputHtml, controlDivClasses, scope, info, options);
|
|
837
872
|
}
|
|
838
873
|
else {
|
|
839
874
|
// Single fields here
|
|
@@ -967,6 +1002,8 @@ var fng;
|
|
|
967
1002
|
callHandleField = false;
|
|
968
1003
|
}
|
|
969
1004
|
else if (info.containerType) {
|
|
1005
|
+
// for now, the following call will only consider security for tabs and not other container types.
|
|
1006
|
+
// hence why we...
|
|
970
1007
|
var parts = containerInstructions(info);
|
|
971
1008
|
switch (info.containerType) {
|
|
972
1009
|
case 'tab':
|
|
@@ -977,9 +1014,12 @@ var fng;
|
|
|
977
1014
|
var activeTabNo = _.findIndex(scope.tabs, function (tab) { return (tab.active); });
|
|
978
1015
|
scope.activeTabNo = activeTabNo >= 0 ? activeTabNo : 0;
|
|
979
1016
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1017
|
+
// ...only check for this here!
|
|
1018
|
+
if (!parts.omit) {
|
|
1019
|
+
result += parts.before;
|
|
1020
|
+
result += processInstructions(info.content, null, options);
|
|
1021
|
+
result += parts.after;
|
|
1022
|
+
}
|
|
983
1023
|
break;
|
|
984
1024
|
case 'tabset':
|
|
985
1025
|
tabsSetup = tabsSetupState.Y;
|
|
@@ -1549,6 +1589,10 @@ var fng;
|
|
|
1549
1589
|
needDivider = false;
|
|
1550
1590
|
parentScope.items.push({ divider: true });
|
|
1551
1591
|
}
|
|
1592
|
+
if (!value.id) {
|
|
1593
|
+
// if it doesn't have an id, give it one, so every menu item is possible to secure
|
|
1594
|
+
value.id = _.camelCase(value.text || value.textFunc() || "");
|
|
1595
|
+
}
|
|
1552
1596
|
parentScope.items.push(value);
|
|
1553
1597
|
}
|
|
1554
1598
|
});
|
|
@@ -1856,7 +1900,7 @@ var fng;
|
|
|
1856
1900
|
* All methods should be state-less
|
|
1857
1901
|
*
|
|
1858
1902
|
*/
|
|
1859
|
-
function formGenerator($
|
|
1903
|
+
function formGenerator($filter, routingService, recordHandler, securityService) {
|
|
1860
1904
|
function handleSchema(description, source, destForm, destList, prefix, doRecursion, $scope, ctrlState) {
|
|
1861
1905
|
function handletabInfo(tabName, thisInst) {
|
|
1862
1906
|
var tabTitle = angular.copy(tabName);
|
|
@@ -2383,7 +2427,7 @@ var fng;
|
|
|
2383
2427
|
}
|
|
2384
2428
|
return result;
|
|
2385
2429
|
},
|
|
2386
|
-
decorateScope: function decorateScope($scope, formGeneratorInstance, recordHandlerInstance, sharedData) {
|
|
2430
|
+
decorateScope: function decorateScope($scope, formGeneratorInstance, recordHandlerInstance, sharedData, pseudoUrl) {
|
|
2387
2431
|
$scope.record = sharedData.record;
|
|
2388
2432
|
$scope.phase = 'init';
|
|
2389
2433
|
$scope.disableFunctions = sharedData.disableFunctions;
|
|
@@ -2400,6 +2444,7 @@ var fng;
|
|
|
2400
2444
|
$scope.pageSize = 60;
|
|
2401
2445
|
$scope.pagesLoaded = 0;
|
|
2402
2446
|
sharedData.baseScope = $scope;
|
|
2447
|
+
securityService.decorateSecurableScope($scope, { pseudoUrl: pseudoUrl });
|
|
2403
2448
|
$scope.generateEditUrl = function (obj) {
|
|
2404
2449
|
return formGeneratorInstance.generateEditUrl(obj, $scope);
|
|
2405
2450
|
};
|
|
@@ -2410,7 +2455,20 @@ var fng;
|
|
|
2410
2455
|
return formGeneratorInstance.generateNewUrl($scope);
|
|
2411
2456
|
};
|
|
2412
2457
|
$scope.scrollTheList = function () {
|
|
2413
|
-
|
|
2458
|
+
var _a;
|
|
2459
|
+
// wait until we have the list schema. until we get a non-empty listSchema (which might never
|
|
2460
|
+
// happen if we don't have permission to GET it), then there's no point requesting the data
|
|
2461
|
+
if (((_a = $scope.listSchema) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
2462
|
+
return recordHandlerInstance.scrollTheList($scope);
|
|
2463
|
+
}
|
|
2464
|
+
else {
|
|
2465
|
+
var unwatch_1 = $scope.$watchCollection("listSchema", function (newValue) {
|
|
2466
|
+
if ((newValue === null || newValue === void 0 ? void 0 : newValue.length) > 0) {
|
|
2467
|
+
unwatch_1();
|
|
2468
|
+
return recordHandlerInstance.scrollTheList($scope);
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2414
2472
|
};
|
|
2415
2473
|
$scope.getListData = function (record, fieldName) {
|
|
2416
2474
|
return recordHandlerInstance.getListData(record, fieldName, $scope.listSchema, $scope);
|
|
@@ -2460,7 +2518,7 @@ var fng;
|
|
|
2460
2518
|
};
|
|
2461
2519
|
}
|
|
2462
2520
|
services.formGenerator = formGenerator;
|
|
2463
|
-
formGenerator.$inject = ["$
|
|
2521
|
+
formGenerator.$inject = ["$filter", "routingService", "recordHandler", "securityService"];
|
|
2464
2522
|
})(services = fng.services || (fng.services = {}));
|
|
2465
2523
|
})(fng || (fng = {}));
|
|
2466
2524
|
/// <reference path="../../index.d.ts" />
|
|
@@ -2469,8 +2527,8 @@ var fng;
|
|
|
2469
2527
|
var services;
|
|
2470
2528
|
(function (services) {
|
|
2471
2529
|
/*@ngInject*/
|
|
2472
|
-
formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "
|
|
2473
|
-
function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService,
|
|
2530
|
+
formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "securityService", "$filter"];
|
|
2531
|
+
function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, securityService, $filter) {
|
|
2474
2532
|
function generateNgShow(showWhen, model) {
|
|
2475
2533
|
function evaluateSide(side) {
|
|
2476
2534
|
var result = side;
|
|
@@ -2508,51 +2566,101 @@ var fng;
|
|
|
2508
2566
|
function glyphClass() {
|
|
2509
2567
|
return (cssFrameworkService.framework() === 'bs2' ? 'icon' : 'glyphicon glyphicon');
|
|
2510
2568
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2569
|
+
// Generate two strings:
|
|
2570
|
+
// 1. firstly, attribute(s) that could be added to element(s) representing the field with the given
|
|
2571
|
+
// parameters to enable or disable it according to the prevailing security rules.
|
|
2572
|
+
// 2. secondly, attribute(s) that could be added to a element - regardless of whether or not it will
|
|
2573
|
+
// actually be disabled on this occasion - to identify it as being potentially disableable
|
|
2574
|
+
// This function is a more complicated version of securityService.generateDisabledAttr, also taking into
|
|
2575
|
+
// account the fact that fieldInfo.readonly can influence the disabled state of a field.
|
|
2576
|
+
// nonUniqueId should be required only in cases where a sub-sub schema has been defined in a directive
|
|
2577
|
+
// as a means of getting around the single-level-of-nesting limitation. in that case, where the
|
|
2578
|
+
// directive's template then includes a <form-input> tag, it is likely that the ids of the sub-sub-schema
|
|
2579
|
+
// elements will include $index from a parent scope (responsible for the sub-schema) in order to
|
|
2580
|
+
// ensure its uniqueness, and in this case (as we are not explicitely managing the addition of the
|
|
2581
|
+
// {{ $index }} expr), we need to be given a version of the id that does not include that expression.
|
|
2582
|
+
// where nonUniqueId is provided, we will also use this for determining ancestors, because in the
|
|
2583
|
+
// nested sub-schema scenario described above, the names of the fields in the sub-sub-schema will
|
|
2584
|
+
// probably not identify the full ancestry.
|
|
2585
|
+
function handleReadOnlyDisabled(partialFieldInfo, scope) {
|
|
2586
|
+
var id = partialFieldInfo.nonUniqueId || partialFieldInfo.id;
|
|
2587
|
+
function getActuallyDisabledAttr() {
|
|
2588
|
+
if (partialFieldInfo.readonly && typeof partialFieldInfo.readonly === "boolean") {
|
|
2589
|
+
// if we have a true-valued readonly property then this trumps whatever security rule might apply to this field
|
|
2526
2590
|
return " disabled ";
|
|
2527
2591
|
}
|
|
2528
|
-
|
|
2592
|
+
function wrapReadOnly() {
|
|
2593
|
+
return partialFieldInfo.readonly ? " ng-disabled=\"".concat(partialFieldInfo.readonly, "\" ") : "";
|
|
2594
|
+
}
|
|
2595
|
+
if (!id || !securityService.canDoSecurityNow(scope, "disabled")) {
|
|
2596
|
+
// no security, so we're just concerned about what value fieldInfo.readonly has
|
|
2529
2597
|
return wrapReadOnly();
|
|
2530
2598
|
}
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
//
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2599
|
+
// if scope has been decorated with a requiresDisabledChildren function, we will be using that to check whether any
|
|
2600
|
+
// of the ancestors of this field's element require their children to be disabled. if they do, that means us!
|
|
2601
|
+
var ancestorIds = [];
|
|
2602
|
+
if (!!scope.requiresDisabledChildren) {
|
|
2603
|
+
var ancestors = void 0;
|
|
2604
|
+
// if we have been provided with a nonUniqueId, we should use that to determine ancestors, because in this case,
|
|
2605
|
+
// the name will not be reliable
|
|
2606
|
+
if (partialFieldInfo.nonUniqueId) {
|
|
2607
|
+
var ancestorStr = partialFieldInfo.nonUniqueId.startsWith("f_") ? partialFieldInfo.nonUniqueId.substring(2) : partialFieldInfo.nonUniqueId;
|
|
2608
|
+
ancestors = ancestorStr.split("_");
|
|
2609
|
+
}
|
|
2610
|
+
else {
|
|
2611
|
+
ancestors = partialFieldInfo.name.split(".");
|
|
2612
|
+
}
|
|
2613
|
+
ancestors.pop();
|
|
2614
|
+
while (ancestors.length > 0) {
|
|
2615
|
+
ancestorIds.push("f_".concat(ancestors.join("_")));
|
|
2616
|
+
ancestors.pop();
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
|
|
2620
|
+
// "instant" security is evaluated now, and a positive result trumps whatever fieldInfo.readonly might be set to
|
|
2621
|
+
if (scope.isSecurelyDisabled(id)) {
|
|
2622
|
+
return " disabled ";
|
|
2623
|
+
}
|
|
2624
|
+
else {
|
|
2625
|
+
for (var _i = 0, ancestorIds_1 = ancestorIds; _i < ancestorIds_1.length; _i++) {
|
|
2626
|
+
var ancestorId = ancestorIds_1[_i];
|
|
2627
|
+
if (scope.requiresDisabledChildren(ancestorId)) {
|
|
2628
|
+
return " disabled ";
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
return wrapReadOnly();
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
var securityFuncStr = "isSecurelyDisabled('".concat(id, "')");
|
|
2635
|
+
if (ancestorIds.length > 0) {
|
|
2636
|
+
var ancestorStr = ancestorIds.map(function (aid) { return "requiresDisabledChildren('".concat(aid, "')"); });
|
|
2637
|
+
securityFuncStr = "(".concat(securityFuncStr, " || ").concat(ancestorStr.join(" || "), ")");
|
|
2638
|
+
}
|
|
2639
|
+
var oneTimeBinding = fng.formsAngular.elemSecurityFuncBinding === "one-time";
|
|
2640
|
+
if (partialFieldInfo.readonly) {
|
|
2641
|
+
// we have both security and a read-only attribute to deal with
|
|
2642
|
+
if (oneTimeBinding) {
|
|
2643
|
+
// if our field has a string-typed readonly attribute *and* one-time binding is required by our securityFunc, we
|
|
2644
|
+
// cannot simply combine these into a single ng-disabled expression, because the readonly property is highly
|
|
2645
|
+
// likely to be model-dependent and therefore cannot use one-time-binding. the best we can do in this case is
|
|
2646
|
+
// to use ng-disabled for the field's readonly property, and a one-time-bound ng-readonly for the securityFunc.
|
|
2647
|
+
// this is not perfect, because in the case of selects, ng-readonly doesn't actually prevent the user from
|
|
2648
|
+
// making a selection. however, the select will be styled as if it is disabled (including the not-allowed
|
|
2649
|
+
// cursor), which should deter the user in most cases.
|
|
2650
|
+
return wrapReadOnly() + "ng-readonly=\"::".concat(securityFuncStr, "\" ");
|
|
2651
|
+
}
|
|
2652
|
+
else {
|
|
2653
|
+
// if we have both things and we are *NOT* required to use one-time binding for the securityFunc, then they can
|
|
2654
|
+
// be combined into a single ng-disabled expression
|
|
2655
|
+
return " ng-disabled=\"".concat(securityFuncStr, " || ").concat(partialFieldInfo.readonly, "\" ");
|
|
2656
|
+
}
|
|
2545
2657
|
}
|
|
2546
2658
|
else {
|
|
2547
|
-
//
|
|
2548
|
-
|
|
2549
|
-
return " ng-disabled=\"".concat(securityFuncStr, " || ").concat(fieldInfo.readonly, "\" ");
|
|
2659
|
+
// we have security only
|
|
2660
|
+
return " ng-disabled=\"".concat(oneTimeBinding ? "::" : "").concat(securityFuncStr, "\" ");
|
|
2550
2661
|
}
|
|
2551
2662
|
}
|
|
2552
|
-
|
|
2553
|
-
// we have security only
|
|
2554
|
-
return " ng-disabled=\"".concat(oneTimeBinding ? "::" : "").concat(securityFuncStr, "\" ");
|
|
2555
|
-
}
|
|
2663
|
+
return [getActuallyDisabledAttr(), securityService.getDisableableAttrs(id)];
|
|
2556
2664
|
}
|
|
2557
2665
|
function generateArrayElementIdString(idString, info, options) {
|
|
2558
2666
|
if (options.subschema && options.model) {
|
|
@@ -2572,6 +2680,9 @@ var fng;
|
|
|
2572
2680
|
return "".concat(idString, "_{{$index}}");
|
|
2573
2681
|
}
|
|
2574
2682
|
}
|
|
2683
|
+
function genDisableableAncestorStr(id) {
|
|
2684
|
+
return securityService.getDisableableAncestorAttrs(id);
|
|
2685
|
+
}
|
|
2575
2686
|
function isArrayElement(scope, info, options) {
|
|
2576
2687
|
return scope["$index"] !== undefined || !!options.subschema;
|
|
2577
2688
|
}
|
|
@@ -2581,22 +2692,41 @@ var fng;
|
|
|
2581
2692
|
fieldChrome: function fieldChrome(scope, info, options) {
|
|
2582
2693
|
var insert = '';
|
|
2583
2694
|
if (info.id && typeof info.id.replace === "function") {
|
|
2584
|
-
var
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2695
|
+
var uniqueIdStr = info.nonUniqueId || info.nonuniqueid || info.id;
|
|
2696
|
+
var idStr = void 0;
|
|
2697
|
+
// replace any . that appear in info.id with "-", but not those that appear between {{ and }}
|
|
2698
|
+
if (info.id.includes(".") && info.id.includes("{{")) {
|
|
2699
|
+
idStr = "cg_";
|
|
2700
|
+
var inExpr = false;
|
|
2701
|
+
for (var i = 0; i < info.id.length; i++) {
|
|
2702
|
+
if (info.id[i] === "{" && info.id[i - 1] === "{") {
|
|
2703
|
+
inExpr = true;
|
|
2704
|
+
}
|
|
2705
|
+
else if (info.id[i] === "}" && info.id[i - 1] === "}") {
|
|
2706
|
+
inExpr = false;
|
|
2707
|
+
}
|
|
2708
|
+
if (inExpr || info.id[i] !== ".") {
|
|
2709
|
+
idStr += info.id[i];
|
|
2710
|
+
}
|
|
2711
|
+
else {
|
|
2712
|
+
idStr += "-";
|
|
2592
2713
|
}
|
|
2593
|
-
;
|
|
2594
|
-
}
|
|
2595
|
-
else {
|
|
2596
|
-
var bindingStr = fng.formsAngular.elemSecurityFuncBinding === "one-time" ? "::" : "";
|
|
2597
|
-
insert += " data-ng-if=\"".concat(bindingStr, "!").concat(fng.formsAngular.elemSecurityFuncName, "('").concat(idStr, "', 'hidden')\"");
|
|
2598
2714
|
}
|
|
2599
2715
|
}
|
|
2716
|
+
else {
|
|
2717
|
+
idStr = "cg_".concat(info.id.replace(/\./g, '-'));
|
|
2718
|
+
}
|
|
2719
|
+
uniqueIdStr = "cg_".concat(uniqueIdStr.replace(/\./g, '-'));
|
|
2720
|
+
var visibility = securityService.considerVisibility(uniqueIdStr, scope);
|
|
2721
|
+
if (visibility.omit) {
|
|
2722
|
+
// we already know this field should be invisible, so we needn't add anything for it
|
|
2723
|
+
return { omit: true };
|
|
2724
|
+
}
|
|
2725
|
+
insert += "id=\"".concat(isArrayElement(scope, info, options) ? generateArrayElementIdString(idStr, info, options) : idStr, "\"");
|
|
2726
|
+
if (visibility.visibilityAttr) {
|
|
2727
|
+
// an angular expression to determine the visibility of this field later...
|
|
2728
|
+
insert += " ".concat(visibility.visibilityAttr);
|
|
2729
|
+
}
|
|
2600
2730
|
}
|
|
2601
2731
|
var classes = info.classes || '';
|
|
2602
2732
|
var template = '';
|
|
@@ -2669,8 +2799,8 @@ var fng;
|
|
|
2669
2799
|
}
|
|
2670
2800
|
labelHTML += addAllService.addAll(scope, 'Label', null, options) + ' class="' + classes + '">' + fieldInfo.label;
|
|
2671
2801
|
if (addButtonMarkup) {
|
|
2672
|
-
var
|
|
2673
|
-
labelHTML += " <i ".concat(
|
|
2802
|
+
var disabledAttrs = handleReadOnlyDisabled(fieldInfo, scope);
|
|
2803
|
+
labelHTML += " <i ".concat(disabledAttrs.join(" "), " id=\"add_").concat(fieldInfo.id, "\" ng-click=\"add('").concat(fieldInfo.name, "', $event)\" class=\"").concat(glyphClass(), "-plus-sign\"></i>");
|
|
2674
2804
|
}
|
|
2675
2805
|
labelHTML += '</label>';
|
|
2676
2806
|
if (fieldInfo.linklabel) {
|
|
@@ -2787,14 +2917,14 @@ var fng;
|
|
|
2787
2917
|
}
|
|
2788
2918
|
return inputMarkup;
|
|
2789
2919
|
},
|
|
2790
|
-
handleArrayInputAndControlDiv: function handleArrayInputAndControlDiv(inputMarkup, controlDivClasses, info, options) {
|
|
2920
|
+
handleArrayInputAndControlDiv: function handleArrayInputAndControlDiv(inputMarkup, controlDivClasses, scope, info, options) {
|
|
2791
2921
|
var indentStr = cssFrameworkService.framework() === 'bs3' ? 'ng-class="skipCols($index)" ' : "";
|
|
2792
2922
|
var arrayStr = (options.model || 'record') + '.' + info.name;
|
|
2793
2923
|
var result = "";
|
|
2794
2924
|
result += '<div id="' + info.id + 'List" class="' + controlDivClasses.join(' ') + '" ' + indentStr + ' ng-repeat="arrayItem in ' + arrayStr + ' track by $index">';
|
|
2795
|
-
var
|
|
2925
|
+
var disabledAttrs = handleReadOnlyDisabled(info, scope);
|
|
2796
2926
|
var removeBtn = info.type !== 'link'
|
|
2797
|
-
? "<i ".concat(
|
|
2927
|
+
? "<i ".concat(disabledAttrs.join(" "), " ng-click=\"remove('").concat(info.name, "', $index, $event)\" id=\"remove_").concat(info.id, "_{{$index}}\" class=\"").concat(glyphClass(), "-minus-sign\"></i>")
|
|
2798
2928
|
: "";
|
|
2799
2929
|
result += inputMarkup.replace("<dms/>", removeBtn);
|
|
2800
2930
|
result += '</div>';
|
|
@@ -2817,7 +2947,8 @@ var fng;
|
|
|
2817
2947
|
return result;
|
|
2818
2948
|
},
|
|
2819
2949
|
handleReadOnlyDisabled: handleReadOnlyDisabled,
|
|
2820
|
-
generateArrayElementIdString: generateArrayElementIdString
|
|
2950
|
+
generateArrayElementIdString: generateArrayElementIdString,
|
|
2951
|
+
genDisableableAncestorStr: genDisableableAncestorStr
|
|
2821
2952
|
};
|
|
2822
2953
|
}
|
|
2823
2954
|
services.formMarkupHelper = formMarkupHelper;
|
|
@@ -2856,16 +2987,63 @@ var fng;
|
|
|
2856
2987
|
/*@ngInject*/
|
|
2857
2988
|
pluginHelper.$inject = ["formMarkupHelper"];
|
|
2858
2989
|
function pluginHelper(formMarkupHelper) {
|
|
2859
|
-
function
|
|
2860
|
-
|
|
2990
|
+
function internalGenDisabledAttrs(scope, id, processedAttrs, idSuffix, params) {
|
|
2991
|
+
// Though id will already have the value of idSuffix appended, processedAttrs.info.name will not.
|
|
2992
|
+
// For handleReadOnlyDisabled() to disable "sub-elements" included in a directive template with an idsuffix when their
|
|
2993
|
+
// 'parent' field is disabled, we need the name to include that suffix as if it were an additional level
|
|
2994
|
+
// of field nesting.
|
|
2995
|
+
var name = processedAttrs.info.name;
|
|
2996
|
+
if (idSuffix) {
|
|
2997
|
+
if (params === null || params === void 0 ? void 0 : params.nonUniqueIdSuffix) {
|
|
2998
|
+
// Generally, when genIdAndDisabledStr is called from a directive, the idSuffix will be something like "select"
|
|
2999
|
+
// or "hasValueCheckbox" (thus enabling a single directive to create a template that includes more than one form
|
|
3000
|
+
// element - such as a checkbox and an input - each of which has a unique id).
|
|
3001
|
+
// Where a directive is responsible for creating markup for an whole array of elements, it is likely to include an
|
|
3002
|
+
// ng-repeat in the template that it generates, and in this case, the idSuffix that it passes to genIdAndDisabledStr
|
|
3003
|
+
// will probably include a reference to $index to ensure uniqueness.
|
|
3004
|
+
// Where idSuffix /does/ contain a reference to $index, the directive should provide a version of the idSuffix
|
|
3005
|
+
// in the params object which does NOT include this.
|
|
3006
|
+
// This is what we need to use for the ng-disabled/ng-readonly expression.
|
|
3007
|
+
// (ReallyCare development hint: for an example of where this is needed, see or-opts.ts.)
|
|
3008
|
+
id = id.replace(idSuffix, params.nonUniqueIdSuffix);
|
|
3009
|
+
name += ".".concat(params.nonUniqueIdSuffix);
|
|
3010
|
+
}
|
|
3011
|
+
else {
|
|
3012
|
+
name += ".".concat(idSuffix);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
var attrs = formMarkupHelper.handleReadOnlyDisabled({
|
|
3016
|
+
id: id,
|
|
3017
|
+
name: name,
|
|
3018
|
+
nonUniqueId: processedAttrs.info.nonuniqueid,
|
|
3019
|
+
readonly: processedAttrs.info.readonly
|
|
3020
|
+
}, scope);
|
|
2861
3021
|
// some types of control (such as ui-select) don't deal correctly with a DISABLED attribute and
|
|
2862
3022
|
// need ng-disabled, even when the expression is simply "true"
|
|
2863
|
-
if (
|
|
2864
|
-
|
|
3023
|
+
if (params === null || params === void 0 ? void 0 : params.forceNg) {
|
|
3024
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
3025
|
+
if (attrs[i].toLowerCase().trim() === "disabled") {
|
|
3026
|
+
attrs[i] = 'ng-disabled="true"';
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
2865
3029
|
}
|
|
2866
|
-
|
|
2867
|
-
|
|
3030
|
+
return attrs;
|
|
3031
|
+
}
|
|
3032
|
+
function internalGenDisabledStr(scope, id, processedAttrs, idSuffix, params) {
|
|
3033
|
+
return internalGenDisabledAttrs(scope, id, processedAttrs, idSuffix, params).join(" ");
|
|
3034
|
+
}
|
|
3035
|
+
// text surrounded by @@ @@ is assumed to be something that can have a pseudonym. We'll rely
|
|
3036
|
+
// upon the relevant controller assigning a pseudo() function to baseScope.
|
|
3037
|
+
function handlePseudos(str) {
|
|
3038
|
+
if (!str) {
|
|
3039
|
+
return str;
|
|
2868
3040
|
}
|
|
3041
|
+
var result = str;
|
|
3042
|
+
while (result.includes("@@")) {
|
|
3043
|
+
result = result.replace("@@", "{{ baseScope.pseudo('");
|
|
3044
|
+
result = result.replace("@@", "', true) }}");
|
|
3045
|
+
}
|
|
3046
|
+
return result;
|
|
2869
3047
|
}
|
|
2870
3048
|
function makeIdStringUniqueForArrayElements(scope, processedAttrs, idString) {
|
|
2871
3049
|
if (formMarkupHelper.isArrayElement(scope, processedAttrs.info, processedAttrs.options)) {
|
|
@@ -2888,8 +3066,11 @@ var fng;
|
|
|
2888
3066
|
}
|
|
2889
3067
|
return result;
|
|
2890
3068
|
}
|
|
2891
|
-
function internalGenDateTimePickerDisabledStr(processedAttrs, idString) {
|
|
2892
|
-
var
|
|
3069
|
+
function internalGenDateTimePickerDisabledStr(scope, processedAttrs, idSuffix, idString) {
|
|
3070
|
+
var rawDisabledAttrs = internalGenDisabledAttrs(scope, idString, processedAttrs, idSuffix, { forceNg: true });
|
|
3071
|
+
// first, we need to convert the 'disabled' attribute(s) (those which might actually cause the element to be
|
|
3072
|
+
// disabled - found in rawDisabledAttrs[0]) into something that the datetime picker understands.
|
|
3073
|
+
var rawDisabledStr = rawDisabledAttrs[0];
|
|
2893
3074
|
var disabledStr = "";
|
|
2894
3075
|
// disabledStr might now include an ng-disabled attribute. To disable both the date and time inputs, we need to
|
|
2895
3076
|
// take the value of that attribute and wrap it up as two new attributes: "disabledDate" and "readonlyTime"
|
|
@@ -2921,7 +3102,9 @@ var fng;
|
|
|
2921
3102
|
// give up
|
|
2922
3103
|
}
|
|
2923
3104
|
}
|
|
2924
|
-
|
|
3105
|
+
// finally, we should add the 'disableable' attribute(s), which might be present in rawDisabledAttrs[1] (regardless
|
|
3106
|
+
// of whether or not the datetime picker is actually disabled) to indicate that it potentially could be
|
|
3107
|
+
return disabledStr + " " + rawDisabledAttrs[1];
|
|
2925
3108
|
}
|
|
2926
3109
|
function extractFromAttr(attr, directiveName) {
|
|
2927
3110
|
function deserialize(str) {
|
|
@@ -2961,7 +3144,19 @@ var fng;
|
|
|
2961
3144
|
}
|
|
2962
3145
|
}
|
|
2963
3146
|
}
|
|
2964
|
-
|
|
3147
|
+
var result = { info: info, options: options, directiveOptions: directiveOptions };
|
|
3148
|
+
// any part of the help text or label that is surrounded by @@ @@ is assumed to be something that can have
|
|
3149
|
+
// a pseudonym. We'll be relying upon the parent controller assigning a pseudo() function to baseScope to
|
|
3150
|
+
// actually perform the translation.
|
|
3151
|
+
// TODO - do this better when fng is re-written!
|
|
3152
|
+
result.info.help = handlePseudos(result.info.help);
|
|
3153
|
+
result.info.label = handlePseudos(result.info.label);
|
|
3154
|
+
return result;
|
|
3155
|
+
}
|
|
3156
|
+
function genIdAndDisabledStr(scope, processedAttrs, idSuffix, params) {
|
|
3157
|
+
var idStr = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3158
|
+
var uniqueIdStr = makeIdStringUniqueForArrayElements(scope, processedAttrs, idStr);
|
|
3159
|
+
return "id=\"".concat(uniqueIdStr, "\" ").concat(internalGenDisabledStr(scope, idStr, processedAttrs, idSuffix, params));
|
|
2965
3160
|
}
|
|
2966
3161
|
return {
|
|
2967
3162
|
extractFromAttr: extractFromAttr,
|
|
@@ -3006,37 +3201,47 @@ var fng;
|
|
|
3006
3201
|
}
|
|
3007
3202
|
var buildingBlocks = formMarkupHelper.allInputsVars(scope, info, options, modelString, idString, nameString);
|
|
3008
3203
|
buildingBlocks.modelString = modelString;
|
|
3204
|
+
buildingBlocks.disableableAncestorStr = formMarkupHelper.genDisableableAncestorStr(info.id);
|
|
3009
3205
|
// defer to the calling directive to generate the markup for the input(s)
|
|
3010
3206
|
var inputHtml = generateInputControl(buildingBlocks);
|
|
3011
3207
|
// wrap this in a div that puts it into the correct bootstrap 'column' and adds validation messages and help text
|
|
3012
3208
|
var wrappedInputHtml = formMarkupHelper.inputChrome(inputHtml, info, options, buildingBlocks);
|
|
3013
3209
|
// further wrap this to add the control div classes, and in the case of an array, the button that allows array elements to be removed
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3210
|
+
if (params.addButtons) {
|
|
3211
|
+
elementHtml += formMarkupHelper.handleArrayInputAndControlDiv(wrappedInputHtml, controlDivClasses, scope, info, options);
|
|
3212
|
+
}
|
|
3213
|
+
else {
|
|
3214
|
+
elementHtml += formMarkupHelper.handleInputAndControlDiv(wrappedInputHtml, controlDivClasses);
|
|
3215
|
+
}
|
|
3018
3216
|
elementHtml += fieldChrome.closeTag;
|
|
3019
3217
|
return elementHtml;
|
|
3020
3218
|
},
|
|
3021
3219
|
genIdString: function genIdString(scope, processedAttrs, idSuffix) {
|
|
3022
3220
|
return internalGenIdString(scope, processedAttrs, idSuffix, true);
|
|
3023
3221
|
},
|
|
3024
|
-
genDisabledStr: function genDisabledStr(scope, processedAttrs, idSuffix,
|
|
3025
|
-
var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3026
|
-
return internalGenDisabledStr(idString, processedAttrs, forceNg);
|
|
3027
|
-
},
|
|
3028
|
-
genIdAndDisabledStr: function genIdAndDisabledStr(scope, processedAttrs, idSuffix, forceNg) {
|
|
3222
|
+
genDisabledStr: function genDisabledStr(scope, processedAttrs, idSuffix, params) {
|
|
3029
3223
|
var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3030
|
-
return
|
|
3224
|
+
return internalGenDisabledStr(scope, idString, processedAttrs, idSuffix, params);
|
|
3031
3225
|
},
|
|
3226
|
+
genIdAndDisabledStr: genIdAndDisabledStr,
|
|
3032
3227
|
genDateTimePickerDisabledStr: function genDateTimePickerDisabledStr(scope, processedAttrs, idSuffix) {
|
|
3033
3228
|
var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3034
|
-
return internalGenDateTimePickerDisabledStr(processedAttrs, idString);
|
|
3229
|
+
return internalGenDateTimePickerDisabledStr(scope, processedAttrs, idSuffix, idString);
|
|
3035
3230
|
},
|
|
3036
3231
|
genDateTimePickerIdAndDisabledStr: function genDateTimePickerIdAndDisabledStr(scope, processedAttrs, idSuffix) {
|
|
3037
|
-
var
|
|
3038
|
-
|
|
3232
|
+
var idStr = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3233
|
+
var uniqueIdStr = makeIdStringUniqueForArrayElements(scope, processedAttrs, idStr);
|
|
3234
|
+
return "id=\"".concat(uniqueIdStr, "\" ").concat(internalGenDateTimePickerDisabledStr(scope, processedAttrs, idSuffix, idStr));
|
|
3235
|
+
},
|
|
3236
|
+
genUiSelectIdAndDisabledStr: function genUiSelectIdAndDisabledStr(scope, processedAttrs, idSuffix) {
|
|
3237
|
+
// ui-select won't be disabled when a simple DISABLED attribute is provided - it requires
|
|
3238
|
+
// ng-disabled even when the value is simply "true"
|
|
3239
|
+
return genIdAndDisabledStr(scope, processedAttrs, idSuffix, { forceNg: true });
|
|
3039
3240
|
},
|
|
3241
|
+
handlePseudos: handlePseudos,
|
|
3242
|
+
genDisableableAncestorStr: function genDisableableAncestorStr(processedAttrs) {
|
|
3243
|
+
return formMarkupHelper.genDisableableAncestorStr(processedAttrs.info.id);
|
|
3244
|
+
}
|
|
3040
3245
|
};
|
|
3041
3246
|
}
|
|
3042
3247
|
services.pluginHelper = pluginHelper;
|
|
@@ -3054,8 +3259,8 @@ var fng;
|
|
|
3054
3259
|
*
|
|
3055
3260
|
*/
|
|
3056
3261
|
/*@ngInject*/
|
|
3057
|
-
recordHandler.$inject = ["$location", "$window", "$filter", "$timeout", "routingService", "cssFrameworkService", "SubmissionsService", "SchemasService"];
|
|
3058
|
-
function recordHandler($location, $window, $filter, $timeout, routingService, cssFrameworkService, SubmissionsService, SchemasService) {
|
|
3262
|
+
recordHandler.$inject = ["$location", "$window", "$filter", "$timeout", "$sce", "routingService", "cssFrameworkService", "SubmissionsService", "SchemasService"];
|
|
3263
|
+
function recordHandler($location, $window, $filter, $timeout, $sce, routingService, cssFrameworkService, SubmissionsService, SchemasService) {
|
|
3059
3264
|
// TODO: Put this in a service
|
|
3060
3265
|
var makeMongoId = function (rnd) {
|
|
3061
3266
|
if (rnd === void 0) { rnd = function (r16) { return Math.floor(r16).toString(16); }; }
|
|
@@ -3662,7 +3867,7 @@ var fng;
|
|
|
3662
3867
|
}
|
|
3663
3868
|
function handleError($scope) {
|
|
3664
3869
|
return function (response) {
|
|
3665
|
-
if ([200, 400].indexOf(response.status) !== -1) {
|
|
3870
|
+
if ([200, 400, 403].indexOf(response.status) !== -1) {
|
|
3666
3871
|
var errorMessage = "";
|
|
3667
3872
|
if (response.data && response.data.errors) {
|
|
3668
3873
|
for (var errorField in response.data.errors) {
|
|
@@ -3686,6 +3891,9 @@ var fng;
|
|
|
3686
3891
|
else {
|
|
3687
3892
|
errorMessage = response.data.message || response.data._message || response.data.err || "Error! Sorry - No further details available.";
|
|
3688
3893
|
}
|
|
3894
|
+
// anyone using a watch on $scope.phase, and waiting for it to become "ready" before proceeding, will probably
|
|
3895
|
+
// want to know that an error has occurred. This value is NOT used anywhere in forms-angular.
|
|
3896
|
+
$scope.phase = "error";
|
|
3689
3897
|
$scope.showError(errorMessage);
|
|
3690
3898
|
}
|
|
3691
3899
|
else {
|
|
@@ -3748,12 +3956,22 @@ var fng;
|
|
|
3748
3956
|
find: $location.$$search.f,
|
|
3749
3957
|
limit: $scope.pageSize,
|
|
3750
3958
|
skip: pagesLoaded * $scope.pageSize,
|
|
3751
|
-
order: $location.$$search.o
|
|
3959
|
+
order: $location.$$search.o,
|
|
3960
|
+
concatenate: false
|
|
3752
3961
|
})
|
|
3753
3962
|
.then(function (response) {
|
|
3754
3963
|
var data = response.data;
|
|
3755
3964
|
if (angular.isArray(data)) {
|
|
3756
|
-
//
|
|
3965
|
+
// if the options for the resource identified by $scope.modelName has disambiguation parameters,
|
|
3966
|
+
// and that resource has more than one list field, the items returned by getPagedAndFilteredList
|
|
3967
|
+
// might include a "disambiguation" property. for this to appear on the list page, we need
|
|
3968
|
+
// to add an item for it to the list schema
|
|
3969
|
+
if (!$scope.listSchema.find(function (f) { return f.name === "disambiguation"; }) && data.some(function (d) { return d.disambiguation; })) {
|
|
3970
|
+
$scope.listSchema.push({
|
|
3971
|
+
name: "disambiguation",
|
|
3972
|
+
});
|
|
3973
|
+
}
|
|
3974
|
+
// I have seen an intermittent problem where a page is requested twice
|
|
3757
3975
|
if (pagesLoaded === $scope.pagesLoaded) {
|
|
3758
3976
|
$scope.pagesLoaded++;
|
|
3759
3977
|
$scope.recordList = $scope.recordList.concat(data);
|
|
@@ -3832,50 +4050,47 @@ var fng;
|
|
|
3832
4050
|
},
|
|
3833
4051
|
getListData: getListData,
|
|
3834
4052
|
suffixCleanId: suffixCleanId,
|
|
4053
|
+
getData: getData,
|
|
3835
4054
|
setData: setData,
|
|
3836
4055
|
setUpLookupOptions: function setUpLookupOptions(lookupCollection, schemaElement, $scope, ctrlState, handleSchema) {
|
|
3837
4056
|
var optionsList = $scope[schemaElement.options] = [];
|
|
3838
4057
|
var idList = $scope[schemaElement.ids] = [];
|
|
3839
|
-
|
|
4058
|
+
var dataRequest = !!schemaElement.filter
|
|
4059
|
+
? SubmissionsService.getPagedAndFilteredList(lookupCollection, Object.assign({ concatenate: true }, schemaElement.filter)) // { concatenate: true } causes it to concatenate the list fields into the .text property of ILookupItem objects
|
|
4060
|
+
: SubmissionsService.getAllListAttributes(lookupCollection);
|
|
4061
|
+
dataRequest
|
|
3840
4062
|
.then(function (response) {
|
|
3841
|
-
var
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
dataRequest
|
|
3852
|
-
.then(function (response) {
|
|
3853
|
-
var data = angular.copy(response.data);
|
|
3854
|
-
if (data) {
|
|
3855
|
-
for (var i = 0; i < data.length; i++) {
|
|
3856
|
-
var option = "";
|
|
3857
|
-
for (var j = 0; j < listInstructions.length; j++) {
|
|
3858
|
-
var thisVal = data[i][listInstructions[j].name];
|
|
3859
|
-
option += thisVal ? thisVal + " " : "";
|
|
4063
|
+
var items = response.data;
|
|
4064
|
+
if (items) {
|
|
4065
|
+
items.sort(function (a, b) { return a.text.localeCompare(b.text); });
|
|
4066
|
+
optionsList.push.apply(optionsList, items.map(function (i) { return i.text; }));
|
|
4067
|
+
idList.push.apply(idList, items.map(function (i) { return i.id; }));
|
|
4068
|
+
var dupes = new Set();
|
|
4069
|
+
for (var i = 0; i < optionsList.length - 1; i++) {
|
|
4070
|
+
for (var j = i + 1; j < optionsList.length; j++) {
|
|
4071
|
+
if (_.isEqual(optionsList[i], optionsList[j])) {
|
|
4072
|
+
dupes.add(optionsList[i]);
|
|
3860
4073
|
}
|
|
3861
|
-
option = option.trim();
|
|
3862
|
-
var pos = _.sortedIndex(optionsList, option);
|
|
3863
|
-
// handle dupes (ideally people will use unique indexes to stop them but...)
|
|
3864
|
-
if (optionsList[pos] === option) {
|
|
3865
|
-
option = option + " (" + data[i]._id + ")";
|
|
3866
|
-
pos = _.sortedIndex(optionsList, option);
|
|
3867
|
-
}
|
|
3868
|
-
optionsList.splice(pos, 0, option);
|
|
3869
|
-
idList.splice(pos, 0, data[i]._id);
|
|
3870
4074
|
}
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
4075
|
+
}
|
|
4076
|
+
// append the id to any duplicates to make them unique
|
|
4077
|
+
dupes.forEach(function (d) {
|
|
4078
|
+
for (var i = 0; i < optionsList.length; i++) {
|
|
4079
|
+
if (optionsList[i] === d) {
|
|
4080
|
+
optionsList[i] += "(" + idList[i] + ")";
|
|
4081
|
+
}
|
|
3876
4082
|
}
|
|
4083
|
+
});
|
|
4084
|
+
if ($scope.readingRecord) {
|
|
4085
|
+
$scope.readingRecord
|
|
4086
|
+
.then(function () {
|
|
4087
|
+
updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
|
|
4088
|
+
});
|
|
3877
4089
|
}
|
|
3878
|
-
}
|
|
4090
|
+
}
|
|
4091
|
+
})
|
|
4092
|
+
.catch(function (e) {
|
|
4093
|
+
$scope.handleHttpError(e);
|
|
3879
4094
|
});
|
|
3880
4095
|
},
|
|
3881
4096
|
setUpLookupListOptions: function setUpLookupListOptions(ref, formInstructions, $scope, ctrlState) {
|
|
@@ -3997,7 +4212,7 @@ var fng;
|
|
|
3997
4212
|
$scope.showError = function (error, alertTitle) {
|
|
3998
4213
|
$scope.alertTitle = alertTitle ? alertTitle : "Error!";
|
|
3999
4214
|
if (typeof error === "string") {
|
|
4000
|
-
$scope.errorMessage = error;
|
|
4215
|
+
$scope.errorMessage = $sce.trustAsHtml(error);
|
|
4001
4216
|
}
|
|
4002
4217
|
else if (!error) {
|
|
4003
4218
|
$scope.errorMessage = "An error occurred - that's all we got. Sorry.";
|
|
@@ -4171,10 +4386,11 @@ var fng;
|
|
|
4171
4386
|
}
|
|
4172
4387
|
};
|
|
4173
4388
|
$scope.isCancelDisabled = function () {
|
|
4389
|
+
var _a;
|
|
4174
4390
|
if ($scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine) {
|
|
4175
4391
|
return true;
|
|
4176
4392
|
}
|
|
4177
|
-
else if (typeof $scope.disableFunctions.isCancelDisabled === "function") {
|
|
4393
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isCancelDisabled) === "function") {
|
|
4178
4394
|
return $scope.disableFunctions.isCancelDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
4179
4395
|
}
|
|
4180
4396
|
else {
|
|
@@ -4182,6 +4398,7 @@ var fng;
|
|
|
4182
4398
|
}
|
|
4183
4399
|
};
|
|
4184
4400
|
$scope.isSaveDisabled = function () {
|
|
4401
|
+
var _a;
|
|
4185
4402
|
$scope.whyDisabled = undefined;
|
|
4186
4403
|
var pristine = false;
|
|
4187
4404
|
function generateWhyDisabledMessage(form, subFormName) {
|
|
@@ -4254,7 +4471,7 @@ var fng;
|
|
|
4254
4471
|
if (pristine || !!$scope.whyDisabled) {
|
|
4255
4472
|
return true;
|
|
4256
4473
|
}
|
|
4257
|
-
else if (typeof $scope.disableFunctions.isSaveDisabled !== "function") {
|
|
4474
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isSaveDisabled) !== "function") {
|
|
4258
4475
|
return false;
|
|
4259
4476
|
}
|
|
4260
4477
|
else {
|
|
@@ -4269,10 +4486,11 @@ var fng;
|
|
|
4269
4486
|
}
|
|
4270
4487
|
};
|
|
4271
4488
|
$scope.isDeleteDisabled = function () {
|
|
4489
|
+
var _a;
|
|
4272
4490
|
if (!$scope.id) {
|
|
4273
4491
|
return true;
|
|
4274
4492
|
}
|
|
4275
|
-
else if (typeof $scope.disableFunctions.isDeleteDisabled === "function") {
|
|
4493
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isDeleteDisabled) === "function") {
|
|
4276
4494
|
return $scope.disableFunctions.isDeleteDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
4277
4495
|
}
|
|
4278
4496
|
else {
|
|
@@ -4280,7 +4498,8 @@ var fng;
|
|
|
4280
4498
|
}
|
|
4281
4499
|
};
|
|
4282
4500
|
$scope.isNewDisabled = function () {
|
|
4283
|
-
|
|
4501
|
+
var _a;
|
|
4502
|
+
if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isNewDisabled) === "function") {
|
|
4284
4503
|
return $scope.disableFunctions.isNewDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
4285
4504
|
}
|
|
4286
4505
|
else {
|
|
@@ -4385,6 +4604,222 @@ var fng;
|
|
|
4385
4604
|
})(services = fng.services || (fng.services = {}));
|
|
4386
4605
|
})(fng || (fng = {}));
|
|
4387
4606
|
/// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
|
|
4607
|
+
var fng;
|
|
4608
|
+
(function (fng) {
|
|
4609
|
+
var services;
|
|
4610
|
+
(function (services) {
|
|
4611
|
+
/*@ngInject*/
|
|
4612
|
+
securityService.$inject = ["$rootScope"];
|
|
4613
|
+
function securityService($rootScope) {
|
|
4614
|
+
function canDoSecurity(type) {
|
|
4615
|
+
return (!!fng.formsAngular.elemSecurityFuncBinding &&
|
|
4616
|
+
((type === "hidden" && !!fng.formsAngular.hiddenSecurityFuncName) ||
|
|
4617
|
+
(type === "disabled" && !!fng.formsAngular.disabledSecurityFuncName)));
|
|
4618
|
+
}
|
|
4619
|
+
function canDoSecurityNow(scope, type) {
|
|
4620
|
+
return (canDoSecurity(type) && // we have security configured
|
|
4621
|
+
(
|
|
4622
|
+
// the host app has not (temporarily) disabled this security type (which it might do, as an optimisation, when there are
|
|
4623
|
+
// currently no security rules to apply); and
|
|
4624
|
+
// it has provided the callbacks that are specified in the security configuration; and
|
|
4625
|
+
// the provided scope (if any) has been decorated (by us). pages and popups which aren't form controllers will need to use
|
|
4626
|
+
// (either directly, or through formMarkupHelper), the decorateSecurableScope() function below
|
|
4627
|
+
(type === "hidden" &&
|
|
4628
|
+
$rootScope[fng.formsAngular.hiddenSecurityFuncName] &&
|
|
4629
|
+
(!scope || !!scope.isSecurelyHidden))
|
|
4630
|
+
||
|
|
4631
|
+
(type === "disabled" &&
|
|
4632
|
+
$rootScope[fng.formsAngular.disabledSecurityFuncName] &&
|
|
4633
|
+
(!scope || !!scope.isSecurelyDisabled))));
|
|
4634
|
+
}
|
|
4635
|
+
function isSecurelyHidden(elemId, pseudoUrl) {
|
|
4636
|
+
return $rootScope[fng.formsAngular.hiddenSecurityFuncName](elemId, pseudoUrl);
|
|
4637
|
+
}
|
|
4638
|
+
function getSecureDisabledState(elemId, pseudoUrl) {
|
|
4639
|
+
return $rootScope[fng.formsAngular.disabledSecurityFuncName](elemId, pseudoUrl);
|
|
4640
|
+
}
|
|
4641
|
+
function isSecurelyDisabled(elemId, pseudoUrl) {
|
|
4642
|
+
return !!getSecureDisabledState(elemId, pseudoUrl); // either true or "+"
|
|
4643
|
+
}
|
|
4644
|
+
function getBindingStr() {
|
|
4645
|
+
return fng.formsAngular.elemSecurityFuncBinding === "one-time" ? "::" : "";
|
|
4646
|
+
}
|
|
4647
|
+
function ignoreElemId(elemId) {
|
|
4648
|
+
var _a;
|
|
4649
|
+
return (_a = fng.formsAngular.ignoreIdsForHideableOrDisableableAttrs) === null || _a === void 0 ? void 0 : _a.some(function (id) { return elemId.includes(id); });
|
|
4650
|
+
}
|
|
4651
|
+
function getXableAttrs(elemId, attr) {
|
|
4652
|
+
if (elemId && attr && !ignoreElemId(elemId)) {
|
|
4653
|
+
return " ".concat(attr, " title=\"").concat(elemId, "\"");
|
|
4654
|
+
}
|
|
4655
|
+
else {
|
|
4656
|
+
return "";
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
function getDisableableAttrs(elemId) {
|
|
4660
|
+
// even when an element should not actually be disabled, we should still mark what would otherwise have been a
|
|
4661
|
+
// potentially-disabled element with scope.disableableAttr - where this is set - and where it is set, also set its
|
|
4662
|
+
// title to be the same as its id so that users can learn of its id by hovering over it. this will
|
|
4663
|
+
// help anyone trying to figure out what is the right element id to use for a DOM security rule
|
|
4664
|
+
return getXableAttrs(elemId, fng.formsAngular.disableableAttr);
|
|
4665
|
+
}
|
|
4666
|
+
function getDisableableAncestorAttrs(elemId) {
|
|
4667
|
+
// even when an element should not actually be disabled, we should still mark what would otherwise have been a
|
|
4668
|
+
// potentially-disabled element with scope.disableableAttr - where this is set - and where it is set, also set its
|
|
4669
|
+
// title to be the same as its id so that users can learn of its id by hovering over it. this will
|
|
4670
|
+
// help anyone trying to figure out what is the right element id to use for a DOM security rule
|
|
4671
|
+
return getXableAttrs(elemId, fng.formsAngular.disableableAncestorAttr);
|
|
4672
|
+
}
|
|
4673
|
+
function getHideableAttrs(elemId) {
|
|
4674
|
+
// even when canDoSecurityNow() returns false, we should still mark what would otherwise have been a
|
|
4675
|
+
// potentially-hidden element with scope.hideableAttr, where this is set, and where it is set, also set its
|
|
4676
|
+
// title to be the same as its id so that users can learn of its id by hovering over it. this will
|
|
4677
|
+
// help anyone trying to figure out what is the right element id to use for a DOM security rule
|
|
4678
|
+
return getXableAttrs(elemId, fng.formsAngular.hideableAttr);
|
|
4679
|
+
}
|
|
4680
|
+
return {
|
|
4681
|
+
canDoSecurity: canDoSecurity,
|
|
4682
|
+
canDoSecurityNow: canDoSecurityNow,
|
|
4683
|
+
isSecurelyHidden: isSecurelyHidden,
|
|
4684
|
+
isSecurelyDisabled: isSecurelyDisabled,
|
|
4685
|
+
getHideableAttrs: getHideableAttrs,
|
|
4686
|
+
getDisableableAttrs: getDisableableAttrs,
|
|
4687
|
+
getDisableableAncestorAttrs: getDisableableAncestorAttrs,
|
|
4688
|
+
// whilst initialising new pages and popups, pass their scope here for decoration with functions that can be used to check
|
|
4689
|
+
// the disabled / hidden state of DOM elements on that page according to the prevailing security rules.
|
|
4690
|
+
// if the host app indicates that security checks should be skipped for this page, we will NOT assign the corresponding
|
|
4691
|
+
// functions. the presence of these functions will be checked later by canDoSecurityNow(), which will always be called
|
|
4692
|
+
// before any security logic is applied. this allows security to be bypassed entirely at the request of the host app,
|
|
4693
|
+
// providing an opportunity for optimisation.
|
|
4694
|
+
decorateSecurableScope: function (securableScope, params) {
|
|
4695
|
+
if (canDoSecurity("hidden") && (!fng.formsAngular.skipHiddenSecurityFuncName || (params === null || params === void 0 ? void 0 : params.overrideSkipping) || !$rootScope[fng.formsAngular.skipHiddenSecurityFuncName](params === null || params === void 0 ? void 0 : params.pseudoUrl))) {
|
|
4696
|
+
securableScope.isSecurelyHidden = function (elemId) {
|
|
4697
|
+
return isSecurelyHidden(elemId, params === null || params === void 0 ? void 0 : params.pseudoUrl);
|
|
4698
|
+
};
|
|
4699
|
+
}
|
|
4700
|
+
if (canDoSecurity("disabled") && (!fng.formsAngular.skipDisabledSecurityFuncName || (params === null || params === void 0 ? void 0 : params.overrideSkipping) || !$rootScope[fng.formsAngular.skipDisabledSecurityFuncName](params === null || params === void 0 ? void 0 : params.pseudoUrl))) {
|
|
4701
|
+
securableScope.isSecurelyDisabled = function (elemId) {
|
|
4702
|
+
return isSecurelyDisabled(elemId, params === null || params === void 0 ? void 0 : params.pseudoUrl);
|
|
4703
|
+
};
|
|
4704
|
+
if (!fng.formsAngular.skipDisabledAncestorSecurityFuncName || (params === null || params === void 0 ? void 0 : params.overrideSkipping) || !$rootScope[fng.formsAngular.skipDisabledAncestorSecurityFuncName](params === null || params === void 0 ? void 0 : params.pseudoUrl)) {
|
|
4705
|
+
securableScope.requiresDisabledChildren = function (elemId) {
|
|
4706
|
+
return getSecureDisabledState(elemId, params === null || params === void 0 ? void 0 : params.pseudoUrl) === "+";
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
},
|
|
4711
|
+
doSecurityWhenReady: function (cb) {
|
|
4712
|
+
if (canDoSecurityNow(undefined, "hidden")) {
|
|
4713
|
+
cb();
|
|
4714
|
+
}
|
|
4715
|
+
else if (canDoSecurity("hidden")) {
|
|
4716
|
+
// wait until the hidden security function has been provided (externally) before proceeding with the callback...
|
|
4717
|
+
// we assume here that the hidden security and disabled security functions are both going to be provided at the
|
|
4718
|
+
// same time (and could therefore watch for either of these things)
|
|
4719
|
+
var unwatch_2 = $rootScope.$watch(fng.formsAngular.hiddenSecurityFuncName, function (newValue) {
|
|
4720
|
+
if (newValue) {
|
|
4721
|
+
unwatch_2();
|
|
4722
|
+
cb();
|
|
4723
|
+
}
|
|
4724
|
+
});
|
|
4725
|
+
}
|
|
4726
|
+
},
|
|
4727
|
+
considerVisibility: function (id, scope) {
|
|
4728
|
+
var hideableAttrs = getHideableAttrs(id);
|
|
4729
|
+
if (canDoSecurityNow(scope, "hidden")) {
|
|
4730
|
+
if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
|
|
4731
|
+
if (scope.isSecurelyHidden(id)) {
|
|
4732
|
+
// if our securityFunc supports instant binding and evaluates to true, then nothing needs to be
|
|
4733
|
+
// added to the dom for this field, which we indicate to our caller as follows...
|
|
4734
|
+
return { omit: true };
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
else {
|
|
4738
|
+
return { visibilityAttr: "data-ng-if=\"".concat(getBindingStr(), "!isSecurelyHidden('").concat(id, "')\"").concat(hideableAttrs) };
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
return { visibilityAttr: hideableAttrs };
|
|
4742
|
+
},
|
|
4743
|
+
// consider the visibility of a container whose visibility depends upon at least some of its content being visible.
|
|
4744
|
+
// the container is assumed not to itself be securable (hence it doesn't have an id - or at least, not one we're
|
|
4745
|
+
// concerned about - and it doesn't itself need a "hideable" attribute)
|
|
4746
|
+
considerContainerVisibility: function (contentIds, scope) {
|
|
4747
|
+
if (canDoSecurityNow(scope, "hidden")) {
|
|
4748
|
+
if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
|
|
4749
|
+
if (contentIds.some(function (id) { return !scope.isSecurelyHidden(id); })) {
|
|
4750
|
+
return {};
|
|
4751
|
+
}
|
|
4752
|
+
else {
|
|
4753
|
+
return { omit: true };
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
else {
|
|
4757
|
+
var attrs = contentIds.map(function (id) { return "!isSecurelyHidden('".concat(id, "')"); });
|
|
4758
|
+
return { visibilityAttr: "data-ng-if=\"".concat(getBindingStr(), "(").concat(attrs.join(" || "), ")\"") };
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
return {};
|
|
4762
|
+
},
|
|
4763
|
+
// Generate an attribute that could be added to the element with the given id to enable or disable it
|
|
4764
|
+
// according to the prevailing security rules. In most cases, the attribute will either be "disabled"
|
|
4765
|
+
// (in the case of 'instant' binding) or data-ng-disabled="xxxx" (in the case of one-time or normal
|
|
4766
|
+
// binding). For directives that require something different, use params:
|
|
4767
|
+
// - forceNg will wrap a positive (disabled) result in an angular directive (e.g., data-ng-disabled="true")
|
|
4768
|
+
// rather than returning simply "disabled"
|
|
4769
|
+
// - attrRequiresValue will translate a positive (disabled) result into an attribute with a truthy value
|
|
4770
|
+
// (e.g., disabled="true") rather than returning simply "disabled"
|
|
4771
|
+
// - attr can be used in the case where a directive expects an attribute other than "disabled".
|
|
4772
|
+
// (for example, uib-tab expects "disable").
|
|
4773
|
+
// Even if the element is not be disabled on this occasion, we will always return the value of
|
|
4774
|
+
// fng.formsAngular.disableableAttr - where set - as a way of marking it as potentially disableable.
|
|
4775
|
+
// Because they can also have a readonly attribute which needs to be taken into consideration, this
|
|
4776
|
+
// function is NOT suitable for fields, which are instead handled by fieldformMarkupHelper.handleReadOnlyDisabled().
|
|
4777
|
+
generateDisabledAttr: function (id, scope, params) {
|
|
4778
|
+
function getActuallyDisabledAttrs() {
|
|
4779
|
+
var result = "";
|
|
4780
|
+
if (canDoSecurityNow(scope, "disabled")) {
|
|
4781
|
+
if (!params) {
|
|
4782
|
+
params = {};
|
|
4783
|
+
}
|
|
4784
|
+
if (params.attrRequiresValue && params.forceNg) {
|
|
4785
|
+
throw new Error("Invalid combination of parameters provided to generateDisabledAttr() [attrRequiresValue and forceNg]");
|
|
4786
|
+
}
|
|
4787
|
+
var attr = params.attr || "disabled";
|
|
4788
|
+
if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
|
|
4789
|
+
if (scope.isSecurelyDisabled(id)) {
|
|
4790
|
+
if (params.attrRequiresValue) {
|
|
4791
|
+
return " ".concat(attr, "=\"true\"");
|
|
4792
|
+
}
|
|
4793
|
+
else if (params.forceNg) {
|
|
4794
|
+
result = "true";
|
|
4795
|
+
}
|
|
4796
|
+
else {
|
|
4797
|
+
return " ".concat(attr);
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
else {
|
|
4802
|
+
result = "".concat(getBindingStr(), "isSecurelyDisabled('").concat(id, "')");
|
|
4803
|
+
}
|
|
4804
|
+
if (result) {
|
|
4805
|
+
if (attr === "disabled") {
|
|
4806
|
+
return " data-ng-disabled=\"".concat(result, "\"");
|
|
4807
|
+
}
|
|
4808
|
+
else {
|
|
4809
|
+
return " data-ng-attr-".concat(attr, "=\"").concat(result, "\"");
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
return result;
|
|
4814
|
+
}
|
|
4815
|
+
return getActuallyDisabledAttrs() + getDisableableAttrs(id);
|
|
4816
|
+
},
|
|
4817
|
+
};
|
|
4818
|
+
}
|
|
4819
|
+
services.securityService = securityService;
|
|
4820
|
+
})(services = fng.services || (fng.services = {}));
|
|
4821
|
+
})(fng || (fng = {}));
|
|
4822
|
+
/// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
|
|
4388
4823
|
var ExpirationCache = /** @class */ (function () {
|
|
4389
4824
|
function ExpirationCache(timeout) {
|
|
4390
4825
|
if (timeout === void 0) { timeout = 60 * 1000; }
|
|
@@ -4430,9 +4865,11 @@ var fng;
|
|
|
4430
4865
|
{
|
|
4431
4866
|
aggregate - whether or not to aggregate results (http://docs.mongodb.org/manual/aggregation/)
|
|
4432
4867
|
find - find parameter
|
|
4868
|
+
projection - the fields to return
|
|
4433
4869
|
limit - limit results to this number of records
|
|
4434
4870
|
skip - skip this number of records before returning results
|
|
4435
4871
|
order - sort order
|
|
4872
|
+
concatenate - whether to concatenate all of the list fields into a single text field (and return { id, text }[]), or not (in which case the documents - albeit only list fields and _id - are returned without transformation)
|
|
4436
4873
|
}
|
|
4437
4874
|
*/
|
|
4438
4875
|
var generateListQuery = function (options) {
|
|
@@ -4453,9 +4890,11 @@ var fng;
|
|
|
4453
4890
|
};
|
|
4454
4891
|
addParameter('l', options.limit);
|
|
4455
4892
|
addParameter('f', options.find);
|
|
4893
|
+
addParameter('p', options.projection);
|
|
4456
4894
|
addParameter('a', options.aggregate);
|
|
4457
4895
|
addParameter('o', options.order);
|
|
4458
4896
|
addParameter('s', options.skip);
|
|
4897
|
+
addParameter('c', options.concatenate);
|
|
4459
4898
|
return queryString;
|
|
4460
4899
|
};
|
|
4461
4900
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
@@ -4480,12 +4919,35 @@ var fng;
|
|
|
4480
4919
|
// changed: changed
|
|
4481
4920
|
// };
|
|
4482
4921
|
// },
|
|
4483
|
-
|
|
4922
|
+
// return only the list attributes for the given record. where returnRaw is true, the record's
|
|
4923
|
+
// list attributes will be returned without transformation. otherwise, the list attributes will be concatenated
|
|
4924
|
+
// (with spaces) and returned in the form { list: string }
|
|
4925
|
+
getListAttributes: function (ref, id, returnRaw) {
|
|
4484
4926
|
var actualId = typeof id === "string" ? id : id.id || id._id || id.x || id;
|
|
4485
|
-
if (typeof actualId
|
|
4486
|
-
throw new Error("getListAttributes
|
|
4927
|
+
if (typeof actualId === "object") {
|
|
4928
|
+
throw new Error("getListAttributes doesn't expect an object but was provided with ".concat(JSON.stringify(id)));
|
|
4929
|
+
}
|
|
4930
|
+
var queryString = returnRaw ? "?returnRaw=1" : "";
|
|
4931
|
+
return $http.get("/api/".concat(ref, "/").concat(actualId, "/list").concat(queryString), { cache: expCache });
|
|
4932
|
+
},
|
|
4933
|
+
// return only the list attributes for ALL records in the given collection, returning ILookupItem[]
|
|
4934
|
+
getAllListAttributes: function (ref) {
|
|
4935
|
+
return $http.get("/api/".concat(ref, "/listAll"), { cache: expCache });
|
|
4936
|
+
},
|
|
4937
|
+
// return only the list attributes for records in the given collection that satisfy the given query conditions (filter, limit etc.)
|
|
4938
|
+
// return ILookupItem[] if options.concatenate is true, else the raw documents
|
|
4939
|
+
getPagedAndFilteredList: function (ref, options) {
|
|
4940
|
+
if (options.projection) {
|
|
4941
|
+
throw new Error("Cannot use projection option for getPagedAndFilteredList, because it only returns list fields");
|
|
4942
|
+
}
|
|
4943
|
+
if (options.concatenate === undefined) {
|
|
4944
|
+
options.concatenate = false;
|
|
4487
4945
|
}
|
|
4488
|
-
return $http.get(
|
|
4946
|
+
return $http.get("/api/".concat(ref, "/listAll").concat(generateListQuery(options)));
|
|
4947
|
+
},
|
|
4948
|
+
// return ALL attributes for records in the given collection that satisfy the given query conditions (filter, limit etc.)
|
|
4949
|
+
getPagedAndFilteredListFull: function (ref, options) {
|
|
4950
|
+
return $http.get("/api/".concat(ref).concat(generateListQuery(options)));
|
|
4489
4951
|
},
|
|
4490
4952
|
readRecord: function (modelName, id) {
|
|
4491
4953
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
@@ -4493,7 +4955,11 @@ var fng;
|
|
|
4493
4955
|
// if (tabChangeData && tabChangeData.model === modelName && tabChangeData.id === id) {
|
|
4494
4956
|
// retVal = Promise.resolve({data:tabChangeData.record, changed: tabChangeData.changed, master: tabChangeData.master});
|
|
4495
4957
|
// } else {
|
|
4496
|
-
|
|
4958
|
+
var actualId = typeof id === "string" ? id : id.id || id._id || id.x || id;
|
|
4959
|
+
if (typeof actualId === "object") {
|
|
4960
|
+
throw new Error("readRecord doesn't expect an object but was provided with ".concat(JSON.stringify(id)));
|
|
4961
|
+
}
|
|
4962
|
+
return $http.get("/api/".concat(modelName, "/").concat(actualId));
|
|
4497
4963
|
// retVal = $http.get('/api/' + modelName + '/' + id);
|
|
4498
4964
|
// }
|
|
4499
4965
|
// tabChangeData = null;
|
|
@@ -4503,21 +4969,18 @@ var fng;
|
|
|
4503
4969
|
var options = angular.extend({
|
|
4504
4970
|
cache: useCacheForGetAll ? expCache : false
|
|
4505
4971
|
}, _options);
|
|
4506
|
-
return $http.get(
|
|
4507
|
-
},
|
|
4508
|
-
getPagedAndFilteredList: function (modelName, options) {
|
|
4509
|
-
return $http.get('/api/' + modelName + generateListQuery(options));
|
|
4972
|
+
return $http.get("/api/".concat(modelName), options);
|
|
4510
4973
|
},
|
|
4511
4974
|
deleteRecord: function (model, id) {
|
|
4512
|
-
return $http.delete(
|
|
4975
|
+
return $http.delete("/api/".concat(model, "/").concat(id));
|
|
4513
4976
|
},
|
|
4514
4977
|
updateRecord: function (modelName, id, dataToSave) {
|
|
4515
|
-
expCache.remove(
|
|
4516
|
-
return $http.post(
|
|
4978
|
+
expCache.remove("/api/".concat(modelName));
|
|
4979
|
+
return $http.post("/api/".concat(modelName, "/").concat(id), dataToSave);
|
|
4517
4980
|
},
|
|
4518
4981
|
createRecord: function (modelName, dataToSave) {
|
|
4519
|
-
expCache.remove(
|
|
4520
|
-
return $http.post(
|
|
4982
|
+
expCache.remove("/api/".concat(modelName));
|
|
4983
|
+
return $http.post("/api/".concat(modelName), dataToSave);
|
|
4521
4984
|
},
|
|
4522
4985
|
useCache: function (val) {
|
|
4523
4986
|
useCacheForGetAll = val;
|
|
@@ -4649,8 +5112,8 @@ var fng;
|
|
|
4649
5112
|
var controllers;
|
|
4650
5113
|
(function (controllers) {
|
|
4651
5114
|
/*@ngInject*/
|
|
4652
|
-
NavCtrl.$inject = ["$rootScope", "$window", "$scope", "$
|
|
4653
|
-
function NavCtrl($rootScope, $window, $scope, $
|
|
5115
|
+
NavCtrl.$inject = ["$rootScope", "$window", "$scope", "$filter", "routingService", "cssFrameworkService", "securityService"];
|
|
5116
|
+
function NavCtrl($rootScope, $window, $scope, $filter, routingService, cssFrameworkService, securityService) {
|
|
4654
5117
|
function clearContextMenu() {
|
|
4655
5118
|
$scope.items = [];
|
|
4656
5119
|
$scope.contextMenu = undefined;
|
|
@@ -4659,12 +5122,6 @@ var fng;
|
|
|
4659
5122
|
$scope.contextMenuDisabled = undefined;
|
|
4660
5123
|
}
|
|
4661
5124
|
$rootScope.navScope = $scope; // Lets plugins access menus
|
|
4662
|
-
$rootScope.isSecurelyHidden = function (elemId) {
|
|
4663
|
-
return fng.formsAngular.elemSecurityFuncName && $rootScope[fng.formsAngular.elemSecurityFuncName](elemId, "hidden");
|
|
4664
|
-
};
|
|
4665
|
-
$rootScope.isSecurelyDisabled = function (elemId) {
|
|
4666
|
-
return fng.formsAngular.elemSecurityFuncName && $rootScope[fng.formsAngular.elemSecurityFuncName](elemId, "disabled");
|
|
4667
|
-
};
|
|
4668
5125
|
clearContextMenu();
|
|
4669
5126
|
$scope.toggleCollapsed = function () {
|
|
4670
5127
|
$scope.collapsed = !$scope.collapsed;
|
|
@@ -4740,15 +5197,19 @@ var fng;
|
|
|
4740
5197
|
}
|
|
4741
5198
|
return result;
|
|
4742
5199
|
};
|
|
4743
|
-
$scope.secureContextMenu = function () {
|
|
4744
|
-
$scope.contextMenuHidden = $rootScope.isSecurelyHidden($scope.contextMenuId);
|
|
4745
|
-
$scope.contextMenuDisabled = $rootScope.isSecurelyDisabled($scope.contextMenuId);
|
|
4746
|
-
};
|
|
4747
5200
|
function initialiseContextMenu(menuCaption) {
|
|
4748
5201
|
$scope.contextMenu = menuCaption;
|
|
4749
5202
|
var menuId = "".concat(_.camelCase(menuCaption), "ContextMenu");
|
|
4750
|
-
|
|
4751
|
-
|
|
5203
|
+
// the context menu itself (see dropdown.ts) has an ng-if that checks for a value of
|
|
5204
|
+
// contextMenuId. let's delete this until we know we're ready to evaluate the security
|
|
5205
|
+
// of the menu items...
|
|
5206
|
+
$scope.contextMenuId = undefined;
|
|
5207
|
+
securityService.doSecurityWhenReady(function () {
|
|
5208
|
+
//... which we now are
|
|
5209
|
+
$scope.contextMenuId = menuId;
|
|
5210
|
+
$scope.contextMenuHidden = securityService.isSecurelyHidden($scope.contextMenuId);
|
|
5211
|
+
$scope.contextMenuDisabled = securityService.isSecurelyDisabled($scope.contextMenuId);
|
|
5212
|
+
});
|
|
4752
5213
|
}
|
|
4753
5214
|
$scope.$on('fngControllersLoaded', function (evt, sharedData, modelName) {
|
|
4754
5215
|
initialiseContextMenu(sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false));
|
|
@@ -4911,7 +5372,8 @@ var fng;
|
|
|
4911
5372
|
.factory('pluginHelper', fng.services.pluginHelper)
|
|
4912
5373
|
.factory('recordHandler', fng.services.recordHandler)
|
|
4913
5374
|
.factory('SchemasService', fng.services.SchemasService)
|
|
4914
|
-
.factory('SubmissionsService', fng.services.SubmissionsService)
|
|
5375
|
+
.factory('SubmissionsService', fng.services.SubmissionsService)
|
|
5376
|
+
.factory('securityService', fng.services.securityService);
|
|
4915
5377
|
})(fng || (fng = {}));
|
|
4916
5378
|
// expose the library
|
|
4917
5379
|
var formsAngular = fng.formsAngular;
|