forms-angular 0.12.0-beta.192 → 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 +972 -250
- package/dist/client/forms-angular.min.js +1 -1
- package/dist/client/index.d.ts +304 -105
- package/dist/server/data_form.js +715 -540
- package/dist/server/index.d.ts +19 -3
- package/package.json +11 -11
|
@@ -4,22 +4,59 @@ var fng;
|
|
|
4
4
|
var directives;
|
|
5
5
|
(function (directives) {
|
|
6
6
|
/*@ngInject*/
|
|
7
|
-
|
|
7
|
+
modelControllerDropdown.$inject = ["securityService"];
|
|
8
|
+
function modelControllerDropdown(securityService) {
|
|
9
|
+
var menuVisibilityStr;
|
|
10
|
+
var menuDisabledStr;
|
|
11
|
+
var itemVisibilityStr = "isHidden($index)";
|
|
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 }}'));
|
|
19
|
+
if (oneTimeBinding) {
|
|
20
|
+
// because the isHidden(...) logic is highly likely to be model dependent, that cannot be one-time bound. to
|
|
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
|
|
22
|
+
itemVisibilityStr = "ng-if=\"::(choice.divider || !isSecurelyHidden(choice.id))\" ng-hide=\"".concat(itemVisibilityStr, "\"");
|
|
23
|
+
}
|
|
24
|
+
else if (fng.formsAngular.elemSecurityFuncBinding === "normal") {
|
|
25
|
+
itemVisibilityStr = "ng-hide=\"".concat(itemVisibilityStr, " || (!choice.divider && isSecurelyHidden(choice.id))\"");
|
|
26
|
+
}
|
|
27
|
+
itemVisibilityStr += " ".concat(securityService.getHideableAttrs('{{ ::choice.id }}'));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
menuVisibilityStr = "";
|
|
31
|
+
itemVisibilityStr = "ng-hide=\"".concat(itemVisibilityStr, "\"");
|
|
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
|
+
}
|
|
8
45
|
return {
|
|
9
|
-
restrict:
|
|
46
|
+
restrict: "AE",
|
|
10
47
|
replace: true,
|
|
11
|
-
template:
|
|
48
|
+
template: "<li id=\"{{ contextMenuId }}\" ng-show=\"items.length > 0\" class=\"mcdd\" uib-dropdown ".concat(menuVisibilityStr, " ").concat(menuDisabledStr, ">") +
|
|
12
49
|
' <a href="#" uib-dropdown-toggle>' +
|
|
13
50
|
' {{contextMenu}} <b class="caret"></b>' +
|
|
14
|
-
|
|
51
|
+
" </a>" +
|
|
15
52
|
' <ul class="uib-dropdown-menu dropdown-menu">' +
|
|
16
|
-
|
|
53
|
+
" <li ng-repeat=\"choice in items\" ng-attr-id=\"{{choice.id}}\" ".concat(itemVisibilityStr, " ").concat(itemDisabledStr, ">") +
|
|
17
54
|
' <a ng-show="choice.text || choice.textFunc" class="dropdown-option" ng-href="{{choice.url || choice.urlFunc()}}" ng-click="doClick($index, $event)">' +
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
55
|
+
" {{ choice.text || choice.textFunc() }}" +
|
|
56
|
+
" </a>" +
|
|
57
|
+
" </li>" +
|
|
58
|
+
" </ul>" +
|
|
59
|
+
"</li>",
|
|
23
60
|
};
|
|
24
61
|
}
|
|
25
62
|
directives.modelControllerDropdown = modelControllerDropdown;
|
|
@@ -236,7 +273,7 @@ var fng;
|
|
|
236
273
|
(function (fng) {
|
|
237
274
|
var directives;
|
|
238
275
|
(function (directives) {
|
|
239
|
-
formInput.$inject = ["$compile", "$rootScope", "$filter", "$timeout", "cssFrameworkService", "formGenerator", "formMarkupHelper"];
|
|
276
|
+
formInput.$inject = ["$compile", "$rootScope", "$filter", "$timeout", "cssFrameworkService", "formGenerator", "formMarkupHelper", "securityService"];
|
|
240
277
|
var tabsSetupState;
|
|
241
278
|
(function (tabsSetupState) {
|
|
242
279
|
tabsSetupState[tabsSetupState["Y"] = 0] = "Y";
|
|
@@ -244,7 +281,7 @@ var fng;
|
|
|
244
281
|
tabsSetupState[tabsSetupState["Forced"] = 2] = "Forced";
|
|
245
282
|
})(tabsSetupState || (tabsSetupState = {}));
|
|
246
283
|
/*@ngInject*/
|
|
247
|
-
function formInput($compile, $rootScope, $filter, $timeout, cssFrameworkService, formGenerator, formMarkupHelper) {
|
|
284
|
+
function formInput($compile, $rootScope, $filter, $timeout, cssFrameworkService, formGenerator, formMarkupHelper, securityService) {
|
|
248
285
|
return {
|
|
249
286
|
restrict: 'EA',
|
|
250
287
|
link: function (scope, element, attrs) {
|
|
@@ -288,7 +325,7 @@ var fng;
|
|
|
288
325
|
// <input type="text" class="input-small" placeholder="Email">
|
|
289
326
|
var subkeys = [];
|
|
290
327
|
var tabsSetup = tabsSetupState.N;
|
|
291
|
-
var generateInput = function (fieldInfo, modelString, isRequired,
|
|
328
|
+
var generateInput = function (fieldInfo, modelString, isRequired, options) {
|
|
292
329
|
function generateEnumInstructions() {
|
|
293
330
|
var enumInstruction;
|
|
294
331
|
if (angular.isArray(scope[fieldInfo.options])) {
|
|
@@ -314,6 +351,10 @@ var fng;
|
|
|
314
351
|
}
|
|
315
352
|
return enumInstruction;
|
|
316
353
|
}
|
|
354
|
+
var idString = fieldInfo.id;
|
|
355
|
+
if (fieldInfo.array || options.subschema) {
|
|
356
|
+
idString = formMarkupHelper.generateArrayElementIdString(idString, fieldInfo, options);
|
|
357
|
+
}
|
|
317
358
|
var nameString;
|
|
318
359
|
if (!modelString) {
|
|
319
360
|
var modelBase = (options.model || 'record') + '.';
|
|
@@ -335,7 +376,6 @@ var fng;
|
|
|
335
376
|
}
|
|
336
377
|
else {
|
|
337
378
|
modelString += '[$index].' + lastPart;
|
|
338
|
-
idString = null;
|
|
339
379
|
nameString = compoundName.replace(/\./g, '-');
|
|
340
380
|
}
|
|
341
381
|
}
|
|
@@ -350,28 +390,13 @@ var fng;
|
|
|
350
390
|
isRequired = isRequired || fieldInfo.required;
|
|
351
391
|
var requiredStr = isRequired ? ' required ' : '';
|
|
352
392
|
var enumInstruction;
|
|
353
|
-
function handleReadOnlyDisabled(readonly) {
|
|
354
|
-
var retVal = '';
|
|
355
|
-
if (readonly) {
|
|
356
|
-
// despite the option being "readonly", we should use disabled and ng-disabled rather than their readonly
|
|
357
|
-
// equivalents (which give controls the appearance of being read-only, but don't actually prevent user
|
|
358
|
-
// interaction)
|
|
359
|
-
if (typeof readonly === "boolean") {
|
|
360
|
-
retVal = " disabled ";
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
retVal = " ng-disabled=\"".concat(readonly, "\" ");
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return retVal;
|
|
367
|
-
}
|
|
368
393
|
switch (fieldInfo.type) {
|
|
369
394
|
case 'select':
|
|
370
395
|
if (fieldInfo.select2) {
|
|
371
396
|
value = '<input placeholder="fng-select2 has been removed" readonly>';
|
|
372
397
|
}
|
|
373
398
|
else {
|
|
374
|
-
common += handleReadOnlyDisabled(fieldInfo.
|
|
399
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
375
400
|
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
376
401
|
common += " aria-label=\"".concat(fieldInfo.label && fieldInfo.label !== "" ? fieldInfo.label : fieldInfo.name, "\" ");
|
|
377
402
|
value = '<select ' + common + 'class="' + allInputsVars.formControl.trim() + allInputsVars.compactClass + allInputsVars.sizeClassBS2 + '" ' + requiredStr + '>';
|
|
@@ -425,7 +450,8 @@ var fng;
|
|
|
425
450
|
case 'radio':
|
|
426
451
|
value = '';
|
|
427
452
|
common += requiredStr;
|
|
428
|
-
common += handleReadOnlyDisabled(fieldInfo.
|
|
453
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
454
|
+
;
|
|
429
455
|
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
430
456
|
var separateLines = options.formstyle === 'vertical' || (options.formstyle !== 'inline' && !fieldInfo.inlineRadio);
|
|
431
457
|
if (angular.isArray(fieldInfo.options)) {
|
|
@@ -456,7 +482,8 @@ var fng;
|
|
|
456
482
|
break;
|
|
457
483
|
case 'checkbox':
|
|
458
484
|
common += requiredStr;
|
|
459
|
-
common += handleReadOnlyDisabled(fieldInfo.
|
|
485
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
486
|
+
;
|
|
460
487
|
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
461
488
|
value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
|
|
462
489
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
@@ -465,6 +492,8 @@ var fng;
|
|
|
465
492
|
break;
|
|
466
493
|
default:
|
|
467
494
|
common += formMarkupHelper.addTextInputMarkup(allInputsVars, fieldInfo, requiredStr);
|
|
495
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
496
|
+
;
|
|
468
497
|
if (fieldInfo.type === 'textarea') {
|
|
469
498
|
if (fieldInfo.rows) {
|
|
470
499
|
if (fieldInfo.rows === 'auto') {
|
|
@@ -514,11 +543,12 @@ var fng;
|
|
|
514
543
|
return result;
|
|
515
544
|
};
|
|
516
545
|
var containerInstructions = function (info) {
|
|
517
|
-
var result
|
|
546
|
+
var result;
|
|
518
547
|
if (typeof info.containerType === 'function') {
|
|
519
548
|
result = info.containerType(info);
|
|
520
549
|
}
|
|
521
550
|
else {
|
|
551
|
+
result = {};
|
|
522
552
|
switch (info.containerType) {
|
|
523
553
|
case 'tab':
|
|
524
554
|
var tabNo = -1;
|
|
@@ -531,12 +561,26 @@ var fng;
|
|
|
531
561
|
if (tabNo >= 0) {
|
|
532
562
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
533
563
|
// result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"'
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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>';
|
|
537
583
|
}
|
|
538
|
-
result.before += '>';
|
|
539
|
-
result.after = '</uib-tab>';
|
|
540
584
|
}
|
|
541
585
|
else {
|
|
542
586
|
result.before = '<p>Error! Tab ' + info.title + ' not found in tab list</p>';
|
|
@@ -629,6 +673,9 @@ var fng;
|
|
|
629
673
|
};
|
|
630
674
|
var handleField = function (info, options) {
|
|
631
675
|
var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
|
|
676
|
+
if (fieldChrome.omit) {
|
|
677
|
+
return "";
|
|
678
|
+
}
|
|
632
679
|
var template = fieldChrome.template;
|
|
633
680
|
if (info.schema) {
|
|
634
681
|
var niceName = info.name.replace(/\./g, '_');
|
|
@@ -685,22 +732,44 @@ var fng;
|
|
|
685
732
|
if (info.formStyle === "inline" && info.inlineHeaders) {
|
|
686
733
|
template += generateInlineHeaders(info.schema, options, model, info.inlineHeaders === "always");
|
|
687
734
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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];
|
|
739
|
+
// if we already know that the field is disabled (only possible when formsAngular.elemSecurityFuncBinding === "instant")
|
|
740
|
+
// then we don't need to add the sortable attribute at all
|
|
741
|
+
// otherwise, we need to include the ng-disabled on the <ol> so this can be referenced by the ui-sortable directive
|
|
742
|
+
// (see sortableOptions)
|
|
743
|
+
var sortableStr = info.sortable && disableCond.trim().toLowerCase() !== "disabled"
|
|
744
|
+
? "".concat(disableCond, " ui-sortable=\"sortableOptions\" ng-model=\"").concat(model, "\"")
|
|
745
|
+
: "";
|
|
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);
|
|
751
|
+
template +=
|
|
752
|
+
"<li ng-form id=\"".concat(info.id, "List_{{$index}}\" name=\"form_").concat(niceName, "{{$index}}\" ").concat(disableableAncestorStr) +
|
|
753
|
+
" class=\"".concat(convertFormStyleToClass(info.formStyle)) +
|
|
754
|
+
" ".concat(cssFrameworkService.framework() === 'bs2' ? 'row-fluid' : '') +
|
|
755
|
+
" ".concat(info.inlineHeaders ? 'width-controlled' : '') +
|
|
756
|
+
" ".concat(info.ngClass ? "ng-class:" + info.ngClass : '', "\"") +
|
|
757
|
+
" ng-repeat=\"subDoc in ".concat(model, " track by $index\"") +
|
|
758
|
+
" ".concat(info.filterable ? 'data-ng-hide="subDoc._hidden"' : '') +
|
|
759
|
+
">";
|
|
694
760
|
if (cssFrameworkService.framework() === 'bs2') {
|
|
695
761
|
template += '<div class="row-fluid sub-doc">';
|
|
696
762
|
}
|
|
697
763
|
if (info.noRemove !== true || info.customSubDoc) {
|
|
698
|
-
|
|
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 : "", ">");
|
|
699
768
|
if (typeof info.customSubDoc == 'string') {
|
|
700
769
|
template += info.customSubDoc;
|
|
701
770
|
}
|
|
702
771
|
if (info.noRemove !== true) {
|
|
703
|
-
template += "<button ".concat(info.noRemove ? 'ng-hide="' + info.noRemove + '"' : '', " name=\"remove_").concat(info.id, "_btn\" ng-click=\"remove('").concat(info.name, "', $index, $event)\"");
|
|
772
|
+
template += "<button ".concat(disableCond, " ").concat(info.noRemove ? 'ng-hide="' + info.noRemove + '"' : '', " name=\"remove_").concat(info.id, "_btn\" ng-click=\"remove('").concat(info.name, "', $index, $event)\"");
|
|
704
773
|
if (info.remove) {
|
|
705
774
|
template += ' class="remove-btn btn btn-mini btn-default btn-xs form-btn"><i class="' + formMarkupHelper.glyphClass() + '-minus"></i> Remove';
|
|
706
775
|
}
|
|
@@ -755,7 +824,12 @@ var fng;
|
|
|
755
824
|
else {
|
|
756
825
|
hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, "\"") : '';
|
|
757
826
|
indicatorShowCond = info.noAdd ? "ng-show=\"".concat(info.noAdd, " && ").concat(indicatorShowCond, "\"") : '';
|
|
758
|
-
|
|
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
|
|
829
|
+
footer +=
|
|
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)\">") +
|
|
831
|
+
" <i class=\"".concat(formMarkupHelper.glyphClass(), "-plus\"></i> Add ") +
|
|
832
|
+
"</button>";
|
|
759
833
|
}
|
|
760
834
|
if (info.noneIndicator) {
|
|
761
835
|
footer += "<span ".concat(indicatorShowCond, " class=\"none_indicator\" id=\"no_").concat(info.id, "_indicator\">None</span>");
|
|
@@ -790,12 +864,16 @@ var fng;
|
|
|
790
864
|
throw new Error('Cannot use arrays in an inline or stacked form');
|
|
791
865
|
}
|
|
792
866
|
template += formMarkupHelper.label(scope, info, info.type !== 'link', options);
|
|
793
|
-
|
|
867
|
+
var stashedHelp = info.help;
|
|
868
|
+
delete info.help;
|
|
869
|
+
var inputHtml = generateInput(info, info.type === 'link' ? null : 'arrayItem.x', true, options);
|
|
870
|
+
info.help = stashedHelp;
|
|
871
|
+
template += formMarkupHelper.handleArrayInputAndControlDiv(inputHtml, controlDivClasses, scope, info, options);
|
|
794
872
|
}
|
|
795
873
|
else {
|
|
796
874
|
// Single fields here
|
|
797
875
|
template += formMarkupHelper.label(scope, info, null, options);
|
|
798
|
-
template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required,
|
|
876
|
+
template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, options), controlDivClasses);
|
|
799
877
|
}
|
|
800
878
|
}
|
|
801
879
|
template += fieldChrome.closeTag;
|
|
@@ -924,6 +1002,8 @@ var fng;
|
|
|
924
1002
|
callHandleField = false;
|
|
925
1003
|
}
|
|
926
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...
|
|
927
1007
|
var parts = containerInstructions(info);
|
|
928
1008
|
switch (info.containerType) {
|
|
929
1009
|
case 'tab':
|
|
@@ -934,9 +1014,12 @@ var fng;
|
|
|
934
1014
|
var activeTabNo = _.findIndex(scope.tabs, function (tab) { return (tab.active); });
|
|
935
1015
|
scope.activeTabNo = activeTabNo >= 0 ? activeTabNo : 0;
|
|
936
1016
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
+
}
|
|
940
1023
|
break;
|
|
941
1024
|
case 'tabset':
|
|
942
1025
|
tabsSetup = tabsSetupState.Y;
|
|
@@ -1506,6 +1589,10 @@ var fng;
|
|
|
1506
1589
|
needDivider = false;
|
|
1507
1590
|
parentScope.items.push({ divider: true });
|
|
1508
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
|
+
}
|
|
1509
1596
|
parentScope.items.push(value);
|
|
1510
1597
|
}
|
|
1511
1598
|
});
|
|
@@ -1813,7 +1900,7 @@ var fng;
|
|
|
1813
1900
|
* All methods should be state-less
|
|
1814
1901
|
*
|
|
1815
1902
|
*/
|
|
1816
|
-
function formGenerator($
|
|
1903
|
+
function formGenerator($filter, routingService, recordHandler, securityService) {
|
|
1817
1904
|
function handleSchema(description, source, destForm, destList, prefix, doRecursion, $scope, ctrlState) {
|
|
1818
1905
|
function handletabInfo(tabName, thisInst) {
|
|
1819
1906
|
var tabTitle = angular.copy(tabName);
|
|
@@ -1950,7 +2037,7 @@ var fng;
|
|
|
1950
2037
|
angular.extend(formInstructions, mongooseType.options.form);
|
|
1951
2038
|
}
|
|
1952
2039
|
}
|
|
1953
|
-
if (mongooseType.instance === 'String') {
|
|
2040
|
+
if (mongooseType.instance === 'String' || (mongooseType.instance === 'ObjectID' && formInstructions.asText)) {
|
|
1954
2041
|
if (mongooseOptions.enum) {
|
|
1955
2042
|
formInstructions.type = formInstructions.type || 'select';
|
|
1956
2043
|
if (formInstructions.select2) {
|
|
@@ -2261,14 +2348,19 @@ var fng;
|
|
|
2261
2348
|
return forceNextTime;
|
|
2262
2349
|
},
|
|
2263
2350
|
add: function add(fieldName, $event, $scope, modelOverride) {
|
|
2264
|
-
var _a;
|
|
2351
|
+
var _a, _b;
|
|
2352
|
+
// for buttons, the click event won't fire if the disabled attribute exists, but the same is not true of
|
|
2353
|
+
// icons, so we need to check this for simple array item addition
|
|
2354
|
+
if (((_a = $event === null || $event === void 0 ? void 0 : $event.target) === null || _a === void 0 ? void 0 : _a.hasAttribute) && $event.target.hasAttribute("disabled")) {
|
|
2355
|
+
return $event.preventDefault();
|
|
2356
|
+
}
|
|
2265
2357
|
// check that target element is visible. May not be reliable - see https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
|
|
2266
2358
|
if ($event.target.offsetParent) {
|
|
2267
2359
|
var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
|
|
2268
2360
|
var schemaElement = $scope.formSchema.find(function (f) { return f.name === fieldName; }); // In case someone is using the formSchema directly
|
|
2269
2361
|
var subSchema = schemaElement ? schemaElement.schema : null;
|
|
2270
2362
|
var obj = subSchema ? $scope.setDefaults(subSchema, fieldName + '.') : {};
|
|
2271
|
-
if (typeof ((
|
|
2363
|
+
if (typeof ((_b = $scope.dataEventFunctions) === null || _b === void 0 ? void 0 : _b.onInitialiseNewSubDoc) === "function") {
|
|
2272
2364
|
$scope.dataEventFunctions.onInitialiseNewSubDoc(fieldName, subSchema, obj);
|
|
2273
2365
|
}
|
|
2274
2366
|
arrayField.push(obj);
|
|
@@ -2281,6 +2373,12 @@ var fng;
|
|
|
2281
2373
|
$scope.setFormDirty($event);
|
|
2282
2374
|
},
|
|
2283
2375
|
remove: function remove(fieldName, value, $event, $scope, modelOverride) {
|
|
2376
|
+
var _a;
|
|
2377
|
+
// for buttons, the click event won't fire if the disabled attribute exists, but the same is not true of
|
|
2378
|
+
// icons, so we need to check this for simple array item removal
|
|
2379
|
+
if (((_a = $event === null || $event === void 0 ? void 0 : $event.target) === null || _a === void 0 ? void 0 : _a.hasAttribute) && $event.target.hasAttribute("disabled")) {
|
|
2380
|
+
return $event.preventDefault();
|
|
2381
|
+
}
|
|
2284
2382
|
// Remove an element from an array
|
|
2285
2383
|
var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
|
|
2286
2384
|
var err;
|
|
@@ -2329,7 +2427,7 @@ var fng;
|
|
|
2329
2427
|
}
|
|
2330
2428
|
return result;
|
|
2331
2429
|
},
|
|
2332
|
-
decorateScope: function decorateScope($scope, formGeneratorInstance, recordHandlerInstance, sharedData) {
|
|
2430
|
+
decorateScope: function decorateScope($scope, formGeneratorInstance, recordHandlerInstance, sharedData, pseudoUrl) {
|
|
2333
2431
|
$scope.record = sharedData.record;
|
|
2334
2432
|
$scope.phase = 'init';
|
|
2335
2433
|
$scope.disableFunctions = sharedData.disableFunctions;
|
|
@@ -2346,6 +2444,7 @@ var fng;
|
|
|
2346
2444
|
$scope.pageSize = 60;
|
|
2347
2445
|
$scope.pagesLoaded = 0;
|
|
2348
2446
|
sharedData.baseScope = $scope;
|
|
2447
|
+
securityService.decorateSecurableScope($scope, { pseudoUrl: pseudoUrl });
|
|
2349
2448
|
$scope.generateEditUrl = function (obj) {
|
|
2350
2449
|
return formGeneratorInstance.generateEditUrl(obj, $scope);
|
|
2351
2450
|
};
|
|
@@ -2356,7 +2455,20 @@ var fng;
|
|
|
2356
2455
|
return formGeneratorInstance.generateNewUrl($scope);
|
|
2357
2456
|
};
|
|
2358
2457
|
$scope.scrollTheList = function () {
|
|
2359
|
-
|
|
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
|
+
}
|
|
2360
2472
|
};
|
|
2361
2473
|
$scope.getListData = function (record, fieldName) {
|
|
2362
2474
|
return recordHandlerInstance.getListData(record, fieldName, $scope.listSchema, $scope);
|
|
@@ -2406,7 +2518,7 @@ var fng;
|
|
|
2406
2518
|
};
|
|
2407
2519
|
}
|
|
2408
2520
|
services.formGenerator = formGenerator;
|
|
2409
|
-
formGenerator.$inject = ["$
|
|
2521
|
+
formGenerator.$inject = ["$filter", "routingService", "recordHandler", "securityService"];
|
|
2410
2522
|
})(services = fng.services || (fng.services = {}));
|
|
2411
2523
|
})(fng || (fng = {}));
|
|
2412
2524
|
/// <reference path="../../index.d.ts" />
|
|
@@ -2415,8 +2527,8 @@ var fng;
|
|
|
2415
2527
|
var services;
|
|
2416
2528
|
(function (services) {
|
|
2417
2529
|
/*@ngInject*/
|
|
2418
|
-
formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "$filter"];
|
|
2419
|
-
function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, $filter) {
|
|
2530
|
+
formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "securityService", "$filter"];
|
|
2531
|
+
function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, securityService, $filter) {
|
|
2420
2532
|
function generateNgShow(showWhen, model) {
|
|
2421
2533
|
function evaluateSide(side) {
|
|
2422
2534
|
var result = side;
|
|
@@ -2454,25 +2566,180 @@ var fng;
|
|
|
2454
2566
|
function glyphClass() {
|
|
2455
2567
|
return (cssFrameworkService.framework() === 'bs2' ? 'icon' : 'glyphicon glyphicon');
|
|
2456
2568
|
}
|
|
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
|
|
2590
|
+
return " disabled ";
|
|
2591
|
+
}
|
|
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
|
|
2597
|
+
return wrapReadOnly();
|
|
2598
|
+
}
|
|
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
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
else {
|
|
2659
|
+
// we have security only
|
|
2660
|
+
return " ng-disabled=\"".concat(oneTimeBinding ? "::" : "").concat(securityFuncStr, "\" ");
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
return [getActuallyDisabledAttr(), securityService.getDisableableAttrs(id)];
|
|
2664
|
+
}
|
|
2665
|
+
function generateArrayElementIdString(idString, info, options) {
|
|
2666
|
+
if (options.subschema && options.model) {
|
|
2667
|
+
// for subschemas, it is possible that our model will begin with $parent., or $parent.$parent. (etc). though a bit of
|
|
2668
|
+
// a hack where this does occur (probably where a directive used by a sub-schema is using a nested <form-input>
|
|
2669
|
+
// directive), we need to look for the $index in the same place as our model is looking for data.
|
|
2670
|
+
var model = options.model;
|
|
2671
|
+
var nestedSteps = 0;
|
|
2672
|
+
var stepIndicator = "$parent.";
|
|
2673
|
+
while (model.startsWith(stepIndicator)) {
|
|
2674
|
+
nestedSteps++;
|
|
2675
|
+
model = model.substring(stepIndicator.length);
|
|
2676
|
+
}
|
|
2677
|
+
return "".concat(idString, "_{{").concat(stepIndicator.repeat(nestedSteps), "$index}}");
|
|
2678
|
+
}
|
|
2679
|
+
else {
|
|
2680
|
+
return "".concat(idString, "_{{$index}}");
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
function genDisableableAncestorStr(id) {
|
|
2684
|
+
return securityService.getDisableableAncestorAttrs(id);
|
|
2685
|
+
}
|
|
2686
|
+
function isArrayElement(scope, info, options) {
|
|
2687
|
+
return scope["$index"] !== undefined || !!options.subschema;
|
|
2688
|
+
}
|
|
2457
2689
|
return {
|
|
2458
2690
|
isHorizontalStyle: isHorizontalStyle,
|
|
2691
|
+
isArrayElement: isArrayElement,
|
|
2459
2692
|
fieldChrome: function fieldChrome(scope, info, options) {
|
|
2693
|
+
var insert = '';
|
|
2694
|
+
if (info.id && typeof info.id.replace === "function") {
|
|
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 += "-";
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
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
|
+
}
|
|
2730
|
+
}
|
|
2460
2731
|
var classes = info.classes || '';
|
|
2461
2732
|
var template = '';
|
|
2462
2733
|
var closeTag = '';
|
|
2463
|
-
var insert = '';
|
|
2464
2734
|
info.showWhen = info.showWhen || info.showwhen; // deal with use within a directive
|
|
2465
2735
|
if (info.showWhen) {
|
|
2466
2736
|
if (typeof info.showWhen === 'string') {
|
|
2467
|
-
insert += 'ng-show="' + info.showWhen + '"';
|
|
2737
|
+
insert += ' ng-show="' + info.showWhen + '"';
|
|
2468
2738
|
}
|
|
2469
2739
|
else {
|
|
2470
|
-
insert += 'ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
|
|
2740
|
+
insert += ' ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
|
|
2471
2741
|
}
|
|
2472
2742
|
}
|
|
2473
|
-
if (info.id && typeof info.id.replace === "function") {
|
|
2474
|
-
insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
|
|
2475
|
-
}
|
|
2476
2743
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
2477
2744
|
classes += ' form-group';
|
|
2478
2745
|
if (options.formstyle === 'vertical' && info.size !== 'block-level') {
|
|
@@ -2532,7 +2799,8 @@ var fng;
|
|
|
2532
2799
|
}
|
|
2533
2800
|
labelHTML += addAllService.addAll(scope, 'Label', null, options) + ' class="' + classes + '">' + fieldInfo.label;
|
|
2534
2801
|
if (addButtonMarkup) {
|
|
2535
|
-
|
|
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>");
|
|
2536
2804
|
}
|
|
2537
2805
|
labelHTML += '</label>';
|
|
2538
2806
|
if (fieldInfo.linklabel) {
|
|
@@ -2568,13 +2836,24 @@ var fng;
|
|
|
2568
2836
|
if (['inline', 'stacked'].includes(options.formstyle)) {
|
|
2569
2837
|
placeHolder = placeHolder || fieldInfo.label;
|
|
2570
2838
|
}
|
|
2571
|
-
common = 'data-ng-model="' + modelString + '"'
|
|
2572
|
-
|
|
2839
|
+
common = 'data-ng-model="' + modelString + '"';
|
|
2840
|
+
if (idString) {
|
|
2841
|
+
common += " id=\"".concat(idString, "\"");
|
|
2842
|
+
}
|
|
2843
|
+
if (nameString) {
|
|
2844
|
+
common += " name=\"".concat(nameString, "\"");
|
|
2845
|
+
}
|
|
2846
|
+
else if (idString) {
|
|
2847
|
+
common += " name=\"".concat(idString, "\"");
|
|
2848
|
+
}
|
|
2849
|
+
if (placeHolder) {
|
|
2850
|
+
common += " placeholder=\"".concat(placeHolder, "\"");
|
|
2851
|
+
}
|
|
2573
2852
|
if (fieldInfo.popup) {
|
|
2574
|
-
common +=
|
|
2853
|
+
common += " title=\"".concat(fieldInfo.popup, "\"");
|
|
2575
2854
|
}
|
|
2576
2855
|
if (fieldInfo.ariaLabel) {
|
|
2577
|
-
common +=
|
|
2856
|
+
common += " aria-label=\"".concat(fieldInfo.ariaLabel, "\"");
|
|
2578
2857
|
}
|
|
2579
2858
|
common += addAllService.addAll(scope, 'Field', null, options);
|
|
2580
2859
|
return {
|
|
@@ -2595,6 +2874,9 @@ var fng;
|
|
|
2595
2874
|
var helpMarkup = cssFrameworkService.framework() === 'bs2' ? { el: 'span', cl: 'help-inline' } : { el: 'div', cl: 'help-block' };
|
|
2596
2875
|
value += "<".concat(helpMarkup.el, " class=\"").concat(helpMarkup.cl, "\">").concat(inlineHelp, "</").concat(helpMarkup.el, ">");
|
|
2597
2876
|
}
|
|
2877
|
+
// this is a dummy tag identifying where the input ends and the messages block (that is only visible when the form field is $dirty)
|
|
2878
|
+
// begins. our caller could replace this tag with anything it needs to insert between these two things.
|
|
2879
|
+
value += "<dms/>";
|
|
2598
2880
|
if (!options.noid) {
|
|
2599
2881
|
value += "<div ng-if=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$dirty\" class=\"help-block\">") +
|
|
2600
2882
|
" <div ng-messages=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$error\">") +
|
|
@@ -2635,18 +2917,21 @@ var fng;
|
|
|
2635
2917
|
}
|
|
2636
2918
|
return inputMarkup;
|
|
2637
2919
|
},
|
|
2638
|
-
handleArrayInputAndControlDiv: function handleArrayInputAndControlDiv(inputMarkup, controlDivClasses, info, options) {
|
|
2639
|
-
var
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
}
|
|
2920
|
+
handleArrayInputAndControlDiv: function handleArrayInputAndControlDiv(inputMarkup, controlDivClasses, scope, info, options) {
|
|
2921
|
+
var indentStr = cssFrameworkService.framework() === 'bs3' ? 'ng-class="skipCols($index)" ' : "";
|
|
2922
|
+
var arrayStr = (options.model || 'record') + '.' + info.name;
|
|
2923
|
+
var result = "";
|
|
2924
|
+
result += '<div id="' + info.id + 'List" class="' + controlDivClasses.join(' ') + '" ' + indentStr + ' ng-repeat="arrayItem in ' + arrayStr + ' track by $index">';
|
|
2925
|
+
var disabledAttrs = handleReadOnlyDisabled(info, scope);
|
|
2926
|
+
var removeBtn = info.type !== 'link'
|
|
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>")
|
|
2928
|
+
: "";
|
|
2929
|
+
result += inputMarkup.replace("<dms/>", removeBtn);
|
|
2649
2930
|
result += '</div>';
|
|
2931
|
+
indentStr = cssFrameworkService.framework() === 'bs3' ? 'ng-class="skipCols(' + arrayStr + '.length)" ' : "";
|
|
2932
|
+
if (info.help) {
|
|
2933
|
+
result += '<div class="array-help-block ' + controlDivClasses.join(' ') + '" ' + indentStr + ' id="empty' + info.id + 'ListHelpBlock">' + info.help + '</div>';
|
|
2934
|
+
}
|
|
2650
2935
|
return result;
|
|
2651
2936
|
},
|
|
2652
2937
|
addTextInputMarkup: function addTextInputMarkup(allInputsVars, fieldInfo, requiredStr) {
|
|
@@ -2659,14 +2944,11 @@ var fng;
|
|
|
2659
2944
|
result += ' ' + fieldInfo.add + ' ';
|
|
2660
2945
|
}
|
|
2661
2946
|
result += requiredStr;
|
|
2662
|
-
if (fieldInfo.readonly) {
|
|
2663
|
-
result += " ".concat(typeof fieldInfo.readOnly === 'boolean' ? 'readonly' : 'ng-readonly="' + fieldInfo.readonly + '"', " ");
|
|
2664
|
-
}
|
|
2665
|
-
else {
|
|
2666
|
-
result += ' ';
|
|
2667
|
-
}
|
|
2668
2947
|
return result;
|
|
2669
|
-
}
|
|
2948
|
+
},
|
|
2949
|
+
handleReadOnlyDisabled: handleReadOnlyDisabled,
|
|
2950
|
+
generateArrayElementIdString: generateArrayElementIdString,
|
|
2951
|
+
genDisableableAncestorStr: genDisableableAncestorStr
|
|
2670
2952
|
};
|
|
2671
2953
|
}
|
|
2672
2954
|
services.formMarkupHelper = formMarkupHelper;
|
|
@@ -2705,105 +2987,260 @@ var fng;
|
|
|
2705
2987
|
/*@ngInject*/
|
|
2706
2988
|
pluginHelper.$inject = ["formMarkupHelper"];
|
|
2707
2989
|
function pluginHelper(formMarkupHelper) {
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
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);
|
|
3021
|
+
// some types of control (such as ui-select) don't deal correctly with a DISABLED attribute and
|
|
3022
|
+
// need ng-disabled, even when the expression is simply "true"
|
|
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"';
|
|
2714
3027
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
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;
|
|
3040
|
+
}
|
|
3041
|
+
var result = str;
|
|
3042
|
+
while (result.includes("@@")) {
|
|
3043
|
+
result = result.replace("@@", "{{ baseScope.pseudo('");
|
|
3044
|
+
result = result.replace("@@", "', true) }}");
|
|
3045
|
+
}
|
|
3046
|
+
return result;
|
|
3047
|
+
}
|
|
3048
|
+
function makeIdStringUniqueForArrayElements(scope, processedAttrs, idString) {
|
|
3049
|
+
if (formMarkupHelper.isArrayElement(scope, processedAttrs.info, processedAttrs.options)) {
|
|
3050
|
+
return formMarkupHelper.generateArrayElementIdString(idString, processedAttrs.info, processedAttrs.options);
|
|
3051
|
+
}
|
|
3052
|
+
else {
|
|
3053
|
+
return idString;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
function internalGenIdString(scope, processedAttrs, suffix, makeUniqueForArrayElements) {
|
|
3057
|
+
var result = processedAttrs.info.id;
|
|
3058
|
+
if (suffix) {
|
|
3059
|
+
if (!suffix.startsWith("_")) {
|
|
3060
|
+
result += "_";
|
|
3061
|
+
}
|
|
3062
|
+
result += suffix;
|
|
3063
|
+
}
|
|
3064
|
+
if (makeUniqueForArrayElements) {
|
|
3065
|
+
result = makeIdStringUniqueForArrayElements(scope, processedAttrs, result);
|
|
3066
|
+
}
|
|
3067
|
+
return result;
|
|
3068
|
+
}
|
|
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];
|
|
3074
|
+
var disabledStr = "";
|
|
3075
|
+
// disabledStr might now include an ng-disabled attribute. To disable both the date and time inputs, we need to
|
|
3076
|
+
// take the value of that attribute and wrap it up as two new attributes: "disabledDate" and "readonlyTime"
|
|
3077
|
+
// (which is what the datetimepicker directive is expecting to receive)
|
|
3078
|
+
if (rawDisabledStr) {
|
|
3079
|
+
// disabledStr should contain either 'ng-disabled="xxxx"' or 'ng-readonly="yyyy"', or both.
|
|
3080
|
+
// the values of xxxx and yyyy could be more-or-less anything, and certainly they could include = or ", which
|
|
3081
|
+
// makes parsing hard
|
|
3082
|
+
// our strategy will be to re-format disabledStr as if it was the string representation of an object, and
|
|
3083
|
+
// then parse it. we can then refer to the ng-disabled and ng-readonly attributes of the parsed object.
|
|
3084
|
+
// in the future, perhaps ng-disabled and ng-readonly will be changed to data-ng-disabled and data-ng-readonly
|
|
3085
|
+
rawDisabledStr = rawDisabledStr.replace("data-ng-disabled", "ng-disabled");
|
|
3086
|
+
rawDisabledStr = rawDisabledStr.replace("data-ng-readonly", "ng-readonly");
|
|
3087
|
+
rawDisabledStr = rawDisabledStr.replace("ng-disabled=", '"ng-disabled":');
|
|
3088
|
+
rawDisabledStr = rawDisabledStr.replace("ng-readonly=", '"ng-readonly":');
|
|
3089
|
+
try {
|
|
3090
|
+
rawDisabledStr = "{ ".concat(rawDisabledStr, " }");
|
|
3091
|
+
var disabledObj = JSON.parse(rawDisabledStr);
|
|
3092
|
+
rawDisabledStr = disabledObj["ng-disabled"];
|
|
3093
|
+
// cannot see a way to sensibly deal with both ng-disabled and ng-readonly. Let's just ignore the ng-readonly
|
|
3094
|
+
// for now - with the way handleReadOnlyDisabled is currently written, this means we'll be unable to fully
|
|
3095
|
+
// support a datetime field with a string-typed "readonly" attribute and where fngAngular's elemSecurityFuncBinding
|
|
3096
|
+
// option is set up to "one-time" or "normal".
|
|
3097
|
+
if (rawDisabledStr) {
|
|
3098
|
+
disabledStr = "disabledDate=\"".concat(rawDisabledStr, "\" readonlyTime=\"").concat(rawDisabledStr, "\"");
|
|
2717
3099
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
3100
|
+
}
|
|
3101
|
+
catch (e) {
|
|
3102
|
+
// give up
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
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];
|
|
3108
|
+
}
|
|
3109
|
+
function extractFromAttr(attr, directiveName) {
|
|
3110
|
+
function deserialize(str) {
|
|
3111
|
+
var retVal = str.replace(/"/g, '"');
|
|
3112
|
+
if (retVal === "true") {
|
|
3113
|
+
return true;
|
|
3114
|
+
}
|
|
3115
|
+
else if (retVal === "false") {
|
|
3116
|
+
return false;
|
|
3117
|
+
}
|
|
3118
|
+
else {
|
|
3119
|
+
var num = parseFloat(retVal);
|
|
3120
|
+
if (!isNaN(num) && isFinite(num)) {
|
|
3121
|
+
return num;
|
|
3122
|
+
}
|
|
3123
|
+
else {
|
|
3124
|
+
return retVal;
|
|
2720
3125
|
}
|
|
2721
|
-
return retVal;
|
|
2722
3126
|
}
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
3127
|
+
}
|
|
3128
|
+
var info = {};
|
|
3129
|
+
var options = { formStyle: attr.formstyle };
|
|
3130
|
+
var directiveOptions = {};
|
|
3131
|
+
var directiveNameLength = directiveName ? directiveName.length : 0;
|
|
3132
|
+
var lcDirectiveName = directiveName === null || directiveName === void 0 ? void 0 : directiveName.toLowerCase();
|
|
3133
|
+
for (var prop in attr) {
|
|
3134
|
+
if (attr.hasOwnProperty(prop)) {
|
|
3135
|
+
var lcProp = prop.toLowerCase();
|
|
3136
|
+
if (lcProp.slice(0, 6) === "fngfld") {
|
|
3137
|
+
info[lcProp.slice(6)] = deserialize(attr[prop]);
|
|
3138
|
+
}
|
|
3139
|
+
else if (lcProp.slice(0, 6) === "fngopt") {
|
|
3140
|
+
options[lcProp.slice(6)] = deserialize(attr[prop]);
|
|
3141
|
+
}
|
|
3142
|
+
else if (directiveName && lcProp.slice(0, directiveNameLength) === lcDirectiveName) {
|
|
3143
|
+
directiveOptions[_.kebabCase(prop.slice(directiveNameLength))] = deserialize(attr[prop]);
|
|
2740
3144
|
}
|
|
2741
3145
|
}
|
|
2742
|
-
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
3146
|
+
}
|
|
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));
|
|
3160
|
+
}
|
|
3161
|
+
return {
|
|
3162
|
+
extractFromAttr: extractFromAttr,
|
|
3163
|
+
buildInputMarkup: function buildInputMarkup(scope, attrs, params, generateInputControl) {
|
|
3164
|
+
var processedAttrs = params.processedAttrs || extractFromAttr(attrs, "");
|
|
3165
|
+
var info = {};
|
|
3166
|
+
if (!params.ignoreFieldInfoFromAttrs) {
|
|
3167
|
+
Object.assign(info, processedAttrs.info);
|
|
2753
3168
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
3169
|
+
if (params.fieldInfoOverrides) {
|
|
3170
|
+
Object.assign(info, params.fieldInfoOverrides);
|
|
3171
|
+
}
|
|
3172
|
+
var options = Object.assign({}, processedAttrs.options, params.optionOverrides);
|
|
3173
|
+
var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
|
|
3174
|
+
if (fieldChrome.omit) {
|
|
3175
|
+
return "";
|
|
2758
3176
|
}
|
|
2759
|
-
|
|
3177
|
+
var controlDivClasses = formMarkupHelper.controlDivClasses(options);
|
|
3178
|
+
var elementHtml = fieldChrome.template + formMarkupHelper.label(scope, info, params.addButtons, options);
|
|
3179
|
+
var idString = info.id;
|
|
3180
|
+
if (info.array || options.subschema) {
|
|
3181
|
+
idString = formMarkupHelper.generateArrayElementIdString(idString, info, options);
|
|
3182
|
+
}
|
|
3183
|
+
var modelString = params.addButtons
|
|
3184
|
+
? "arrayItem" + (params.needsX ? ".x" : "")
|
|
3185
|
+
: attrs.model + "." + info.name;
|
|
3186
|
+
var nameString = info.name;
|
|
3187
|
+
if (options.subschema && info.name.indexOf(".") !== -1) {
|
|
2760
3188
|
// Schema handling - need to massage the ngModel and the id
|
|
2761
|
-
var modelBase = model +
|
|
2762
|
-
var compoundName = info.name;
|
|
3189
|
+
var modelBase = attrs.model + ".";
|
|
2763
3190
|
var root = options.subschemaroot;
|
|
2764
|
-
var lastPart =
|
|
2765
|
-
modelString = modelBase;
|
|
2766
|
-
if (options.
|
|
2767
|
-
modelString
|
|
2768
|
-
|
|
3191
|
+
var lastPart = info.name.slice(root.length + 1);
|
|
3192
|
+
modelString = modelBase + root;
|
|
3193
|
+
if (options.subkey) {
|
|
3194
|
+
idString = modelString.slice(modelBase.length).replace(/\./g, "-") + "-subkey" + options.subkeyno + "-" + lastPart;
|
|
3195
|
+
modelString += "[" + "$_arrayOffset_" + root.replace(/\./g, "_") + "_" + options.subkeyno + "]." + lastPart;
|
|
2769
3196
|
}
|
|
2770
3197
|
else {
|
|
2771
|
-
modelString +=
|
|
2772
|
-
|
|
2773
|
-
idString = modelString.slice(modelBase.length).replace(/\./g, '-') + '-subkey' + options.subkeyno + '-' + lastPart;
|
|
2774
|
-
modelString += '[' + '$_arrayOffset_' + root.replace(/\./g, '_') + '_' + options.subkeyno + '].' + lastPart;
|
|
2775
|
-
}
|
|
2776
|
-
else {
|
|
2777
|
-
modelString += '[$index].' + lastPart;
|
|
2778
|
-
idString = null;
|
|
2779
|
-
nameString = compoundName.replace(/\./g, '-');
|
|
2780
|
-
}
|
|
3198
|
+
modelString += "[$index]." + lastPart;
|
|
3199
|
+
nameString = info.name.replace(/\./g, "-");
|
|
2781
3200
|
}
|
|
2782
3201
|
}
|
|
2783
3202
|
var buildingBlocks = formMarkupHelper.allInputsVars(scope, info, options, modelString, idString, nameString);
|
|
2784
3203
|
buildingBlocks.modelString = modelString;
|
|
2785
|
-
|
|
3204
|
+
buildingBlocks.disableableAncestorStr = formMarkupHelper.genDisableableAncestorStr(info.id);
|
|
3205
|
+
// defer to the calling directive to generate the markup for the input(s)
|
|
3206
|
+
var inputHtml = generateInputControl(buildingBlocks);
|
|
3207
|
+
// wrap this in a div that puts it into the correct bootstrap 'column' and adds validation messages and help text
|
|
3208
|
+
var wrappedInputHtml = formMarkupHelper.inputChrome(inputHtml, info, options, buildingBlocks);
|
|
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
|
|
3210
|
+
if (params.addButtons) {
|
|
3211
|
+
elementHtml += formMarkupHelper.handleArrayInputAndControlDiv(wrappedInputHtml, controlDivClasses, scope, info, options);
|
|
3212
|
+
}
|
|
3213
|
+
else {
|
|
3214
|
+
elementHtml += formMarkupHelper.handleInputAndControlDiv(wrappedInputHtml, controlDivClasses);
|
|
3215
|
+
}
|
|
2786
3216
|
elementHtml += fieldChrome.closeTag;
|
|
2787
3217
|
return elementHtml;
|
|
2788
3218
|
},
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
3219
|
+
genIdString: function genIdString(scope, processedAttrs, idSuffix) {
|
|
3220
|
+
return internalGenIdString(scope, processedAttrs, idSuffix, true);
|
|
3221
|
+
},
|
|
3222
|
+
genDisabledStr: function genDisabledStr(scope, processedAttrs, idSuffix, params) {
|
|
3223
|
+
var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3224
|
+
return internalGenDisabledStr(scope, idString, processedAttrs, idSuffix, params);
|
|
3225
|
+
},
|
|
3226
|
+
genIdAndDisabledStr: genIdAndDisabledStr,
|
|
3227
|
+
genDateTimePickerDisabledStr: function genDateTimePickerDisabledStr(scope, processedAttrs, idSuffix) {
|
|
3228
|
+
var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3229
|
+
return internalGenDateTimePickerDisabledStr(scope, processedAttrs, idSuffix, idString);
|
|
3230
|
+
},
|
|
3231
|
+
genDateTimePickerIdAndDisabledStr: function genDateTimePickerIdAndDisabledStr(scope, processedAttrs, idSuffix) {
|
|
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 });
|
|
3240
|
+
},
|
|
3241
|
+
handlePseudos: handlePseudos,
|
|
3242
|
+
genDisableableAncestorStr: function genDisableableAncestorStr(processedAttrs) {
|
|
3243
|
+
return formMarkupHelper.genDisableableAncestorStr(processedAttrs.info.id);
|
|
2807
3244
|
}
|
|
2808
3245
|
};
|
|
2809
3246
|
}
|
|
@@ -2822,8 +3259,8 @@ var fng;
|
|
|
2822
3259
|
*
|
|
2823
3260
|
*/
|
|
2824
3261
|
/*@ngInject*/
|
|
2825
|
-
recordHandler.$inject = ["$location", "$window", "$filter", "$timeout", "routingService", "cssFrameworkService", "SubmissionsService", "SchemasService"];
|
|
2826
|
-
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) {
|
|
2827
3264
|
// TODO: Put this in a service
|
|
2828
3265
|
var makeMongoId = function (rnd) {
|
|
2829
3266
|
if (rnd === void 0) { rnd = function (r16) { return Math.floor(r16).toString(16); }; }
|
|
@@ -3076,18 +3513,12 @@ var fng;
|
|
|
3076
3513
|
var idList = $scope[suffixCleanId(schemaEntry, "_ids")];
|
|
3077
3514
|
var thisConversion = void 0;
|
|
3078
3515
|
if (fieldValue && idList && idList.length > 0) {
|
|
3079
|
-
if (fieldName.indexOf(".") !== -1) {
|
|
3080
|
-
throw new Error("Trying to directly assign to a nested field 332");
|
|
3081
|
-
} // Not sure that this can happen, but put in a runtime test
|
|
3082
3516
|
if (
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
/*
|
|
3089
|
-
We are not suppressing conversions
|
|
3090
|
-
*/
|
|
3517
|
+
// it's not a nested field
|
|
3518
|
+
!fieldName.includes(".") &&
|
|
3519
|
+
// Check we are starting with an ObjectId (ie not being called because of $watch on conversion, with a converted value, which would cause an exception)
|
|
3520
|
+
fieldValue.toString().match(/^[a-f0-9]{24}$/) &&
|
|
3521
|
+
// We are not suppressing conversions
|
|
3091
3522
|
(!schemaEntry.internalRef || !schemaEntry.internalRef.noConvert)) {
|
|
3092
3523
|
anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, "Options")], idList);
|
|
3093
3524
|
}
|
|
@@ -3436,7 +3867,7 @@ var fng;
|
|
|
3436
3867
|
}
|
|
3437
3868
|
function handleError($scope) {
|
|
3438
3869
|
return function (response) {
|
|
3439
|
-
if ([200, 400].indexOf(response.status) !== -1) {
|
|
3870
|
+
if ([200, 400, 403].indexOf(response.status) !== -1) {
|
|
3440
3871
|
var errorMessage = "";
|
|
3441
3872
|
if (response.data && response.data.errors) {
|
|
3442
3873
|
for (var errorField in response.data.errors) {
|
|
@@ -3460,6 +3891,9 @@ var fng;
|
|
|
3460
3891
|
else {
|
|
3461
3892
|
errorMessage = response.data.message || response.data._message || response.data.err || "Error! Sorry - No further details available.";
|
|
3462
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";
|
|
3463
3897
|
$scope.showError(errorMessage);
|
|
3464
3898
|
}
|
|
3465
3899
|
else {
|
|
@@ -3522,12 +3956,22 @@ var fng;
|
|
|
3522
3956
|
find: $location.$$search.f,
|
|
3523
3957
|
limit: $scope.pageSize,
|
|
3524
3958
|
skip: pagesLoaded * $scope.pageSize,
|
|
3525
|
-
order: $location.$$search.o
|
|
3959
|
+
order: $location.$$search.o,
|
|
3960
|
+
concatenate: false
|
|
3526
3961
|
})
|
|
3527
3962
|
.then(function (response) {
|
|
3528
3963
|
var data = response.data;
|
|
3529
3964
|
if (angular.isArray(data)) {
|
|
3530
|
-
//
|
|
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
|
|
3531
3975
|
if (pagesLoaded === $scope.pagesLoaded) {
|
|
3532
3976
|
$scope.pagesLoaded++;
|
|
3533
3977
|
$scope.recordList = $scope.recordList.concat(data);
|
|
@@ -3606,50 +4050,47 @@ var fng;
|
|
|
3606
4050
|
},
|
|
3607
4051
|
getListData: getListData,
|
|
3608
4052
|
suffixCleanId: suffixCleanId,
|
|
4053
|
+
getData: getData,
|
|
3609
4054
|
setData: setData,
|
|
3610
4055
|
setUpLookupOptions: function setUpLookupOptions(lookupCollection, schemaElement, $scope, ctrlState, handleSchema) {
|
|
3611
4056
|
var optionsList = $scope[schemaElement.options] = [];
|
|
3612
4057
|
var idList = $scope[schemaElement.ids] = [];
|
|
3613
|
-
|
|
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
|
|
3614
4062
|
.then(function (response) {
|
|
3615
|
-
var
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
dataRequest
|
|
3626
|
-
.then(function (response) {
|
|
3627
|
-
var data = angular.copy(response.data);
|
|
3628
|
-
if (data) {
|
|
3629
|
-
for (var i = 0; i < data.length; i++) {
|
|
3630
|
-
var option = "";
|
|
3631
|
-
for (var j = 0; j < listInstructions.length; j++) {
|
|
3632
|
-
var thisVal = data[i][listInstructions[j].name];
|
|
3633
|
-
option += thisVal ? thisVal + " " : "";
|
|
3634
|
-
}
|
|
3635
|
-
option = option.trim();
|
|
3636
|
-
var pos = _.sortedIndex(optionsList, option);
|
|
3637
|
-
// handle dupes (ideally people will use unique indexes to stop them but...)
|
|
3638
|
-
if (optionsList[pos] === option) {
|
|
3639
|
-
option = option + " (" + data[i]._id + ")";
|
|
3640
|
-
pos = _.sortedIndex(optionsList, option);
|
|
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]);
|
|
3641
4073
|
}
|
|
3642
|
-
optionsList.splice(pos, 0, option);
|
|
3643
|
-
idList.splice(pos, 0, data[i]._id);
|
|
3644
4074
|
}
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
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
|
+
}
|
|
3650
4082
|
}
|
|
4083
|
+
});
|
|
4084
|
+
if ($scope.readingRecord) {
|
|
4085
|
+
$scope.readingRecord
|
|
4086
|
+
.then(function () {
|
|
4087
|
+
updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
|
|
4088
|
+
});
|
|
3651
4089
|
}
|
|
3652
|
-
}
|
|
4090
|
+
}
|
|
4091
|
+
})
|
|
4092
|
+
.catch(function (e) {
|
|
4093
|
+
$scope.handleHttpError(e);
|
|
3653
4094
|
});
|
|
3654
4095
|
},
|
|
3655
4096
|
setUpLookupListOptions: function setUpLookupListOptions(ref, formInstructions, $scope, ctrlState) {
|
|
@@ -3771,7 +4212,7 @@ var fng;
|
|
|
3771
4212
|
$scope.showError = function (error, alertTitle) {
|
|
3772
4213
|
$scope.alertTitle = alertTitle ? alertTitle : "Error!";
|
|
3773
4214
|
if (typeof error === "string") {
|
|
3774
|
-
$scope.errorMessage = error;
|
|
4215
|
+
$scope.errorMessage = $sce.trustAsHtml(error);
|
|
3775
4216
|
}
|
|
3776
4217
|
else if (!error) {
|
|
3777
4218
|
$scope.errorMessage = "An error occurred - that's all we got. Sorry.";
|
|
@@ -3945,10 +4386,11 @@ var fng;
|
|
|
3945
4386
|
}
|
|
3946
4387
|
};
|
|
3947
4388
|
$scope.isCancelDisabled = function () {
|
|
4389
|
+
var _a;
|
|
3948
4390
|
if ($scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine) {
|
|
3949
4391
|
return true;
|
|
3950
4392
|
}
|
|
3951
|
-
else if (typeof $scope.disableFunctions.isCancelDisabled === "function") {
|
|
4393
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isCancelDisabled) === "function") {
|
|
3952
4394
|
return $scope.disableFunctions.isCancelDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
3953
4395
|
}
|
|
3954
4396
|
else {
|
|
@@ -3956,6 +4398,7 @@ var fng;
|
|
|
3956
4398
|
}
|
|
3957
4399
|
};
|
|
3958
4400
|
$scope.isSaveDisabled = function () {
|
|
4401
|
+
var _a;
|
|
3959
4402
|
$scope.whyDisabled = undefined;
|
|
3960
4403
|
var pristine = false;
|
|
3961
4404
|
function generateWhyDisabledMessage(form, subFormName) {
|
|
@@ -4028,7 +4471,7 @@ var fng;
|
|
|
4028
4471
|
if (pristine || !!$scope.whyDisabled) {
|
|
4029
4472
|
return true;
|
|
4030
4473
|
}
|
|
4031
|
-
else if (typeof $scope.disableFunctions.isSaveDisabled !== "function") {
|
|
4474
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isSaveDisabled) !== "function") {
|
|
4032
4475
|
return false;
|
|
4033
4476
|
}
|
|
4034
4477
|
else {
|
|
@@ -4043,10 +4486,11 @@ var fng;
|
|
|
4043
4486
|
}
|
|
4044
4487
|
};
|
|
4045
4488
|
$scope.isDeleteDisabled = function () {
|
|
4489
|
+
var _a;
|
|
4046
4490
|
if (!$scope.id) {
|
|
4047
4491
|
return true;
|
|
4048
4492
|
}
|
|
4049
|
-
else if (typeof $scope.disableFunctions.isDeleteDisabled === "function") {
|
|
4493
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isDeleteDisabled) === "function") {
|
|
4050
4494
|
return $scope.disableFunctions.isDeleteDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
4051
4495
|
}
|
|
4052
4496
|
else {
|
|
@@ -4054,7 +4498,8 @@ var fng;
|
|
|
4054
4498
|
}
|
|
4055
4499
|
};
|
|
4056
4500
|
$scope.isNewDisabled = function () {
|
|
4057
|
-
|
|
4501
|
+
var _a;
|
|
4502
|
+
if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isNewDisabled) === "function") {
|
|
4058
4503
|
return $scope.disableFunctions.isNewDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
4059
4504
|
}
|
|
4060
4505
|
else {
|
|
@@ -4090,11 +4535,25 @@ var fng;
|
|
|
4090
4535
|
//}
|
|
4091
4536
|
};
|
|
4092
4537
|
$scope.sortableOptions = {
|
|
4093
|
-
update: function () {
|
|
4094
|
-
if (
|
|
4538
|
+
update: function (e, ui) {
|
|
4539
|
+
if (e.target.hasAttribute("disabled")) {
|
|
4540
|
+
// where formsAngular.elemSecurityFuncBinding is set to "one-time" or "normal", the <ol> that the
|
|
4541
|
+
// ui-sortable directive has been used with will have an ng-disabled that may or may not have caused
|
|
4542
|
+
// a disabled attribute to be added to that element. in the case where this attribute has been
|
|
4543
|
+
// added, sorting should be prevented.
|
|
4544
|
+
// allowing the user to begin the drag, and then preventing it only once they release the mouse button,
|
|
4545
|
+
// doesn't seem like the best solution, but I've yet to find something that works better. the
|
|
4546
|
+
// cancel property (see commented-out code below) looks like it should work (and kind of does), but this
|
|
4547
|
+
// screws up mouse events on input fields hosted within the draggable <li> items, so you're
|
|
4548
|
+
// basically prevented from updating any form element in the nested schema
|
|
4549
|
+
ui.item.sortable.cancel();
|
|
4550
|
+
}
|
|
4551
|
+
else if ($scope.topLevelFormName) {
|
|
4095
4552
|
$scope[$scope.topLevelFormName].$setDirty();
|
|
4096
4553
|
}
|
|
4097
|
-
}
|
|
4554
|
+
},
|
|
4555
|
+
// don't do this (see comment above)
|
|
4556
|
+
//cancel: "ol[disabled]>li"
|
|
4098
4557
|
};
|
|
4099
4558
|
$scope.setUpCustomLookupOptions = function (schemaElement, ids, options, baseScope) {
|
|
4100
4559
|
for (var _i = 0, _a = [$scope, baseScope]; _i < _a.length; _i++) {
|
|
@@ -4145,6 +4604,222 @@ var fng;
|
|
|
4145
4604
|
})(services = fng.services || (fng.services = {}));
|
|
4146
4605
|
})(fng || (fng = {}));
|
|
4147
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" />
|
|
4148
4823
|
var ExpirationCache = /** @class */ (function () {
|
|
4149
4824
|
function ExpirationCache(timeout) {
|
|
4150
4825
|
if (timeout === void 0) { timeout = 60 * 1000; }
|
|
@@ -4190,9 +4865,11 @@ var fng;
|
|
|
4190
4865
|
{
|
|
4191
4866
|
aggregate - whether or not to aggregate results (http://docs.mongodb.org/manual/aggregation/)
|
|
4192
4867
|
find - find parameter
|
|
4868
|
+
projection - the fields to return
|
|
4193
4869
|
limit - limit results to this number of records
|
|
4194
4870
|
skip - skip this number of records before returning results
|
|
4195
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)
|
|
4196
4873
|
}
|
|
4197
4874
|
*/
|
|
4198
4875
|
var generateListQuery = function (options) {
|
|
@@ -4213,9 +4890,11 @@ var fng;
|
|
|
4213
4890
|
};
|
|
4214
4891
|
addParameter('l', options.limit);
|
|
4215
4892
|
addParameter('f', options.find);
|
|
4893
|
+
addParameter('p', options.projection);
|
|
4216
4894
|
addParameter('a', options.aggregate);
|
|
4217
4895
|
addParameter('o', options.order);
|
|
4218
4896
|
addParameter('s', options.skip);
|
|
4897
|
+
addParameter('c', options.concatenate);
|
|
4219
4898
|
return queryString;
|
|
4220
4899
|
};
|
|
4221
4900
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
@@ -4240,12 +4919,35 @@ var fng;
|
|
|
4240
4919
|
// changed: changed
|
|
4241
4920
|
// };
|
|
4242
4921
|
// },
|
|
4243
|
-
|
|
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) {
|
|
4244
4926
|
var actualId = typeof id === "string" ? id : id.id || id._id || id.x || id;
|
|
4245
|
-
if (typeof actualId
|
|
4246
|
-
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");
|
|
4247
4942
|
}
|
|
4248
|
-
|
|
4943
|
+
if (options.concatenate === undefined) {
|
|
4944
|
+
options.concatenate = false;
|
|
4945
|
+
}
|
|
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)));
|
|
4249
4951
|
},
|
|
4250
4952
|
readRecord: function (modelName, id) {
|
|
4251
4953
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
@@ -4253,7 +4955,11 @@ var fng;
|
|
|
4253
4955
|
// if (tabChangeData && tabChangeData.model === modelName && tabChangeData.id === id) {
|
|
4254
4956
|
// retVal = Promise.resolve({data:tabChangeData.record, changed: tabChangeData.changed, master: tabChangeData.master});
|
|
4255
4957
|
// } else {
|
|
4256
|
-
|
|
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));
|
|
4257
4963
|
// retVal = $http.get('/api/' + modelName + '/' + id);
|
|
4258
4964
|
// }
|
|
4259
4965
|
// tabChangeData = null;
|
|
@@ -4263,21 +4969,18 @@ var fng;
|
|
|
4263
4969
|
var options = angular.extend({
|
|
4264
4970
|
cache: useCacheForGetAll ? expCache : false
|
|
4265
4971
|
}, _options);
|
|
4266
|
-
return $http.get(
|
|
4267
|
-
},
|
|
4268
|
-
getPagedAndFilteredList: function (modelName, options) {
|
|
4269
|
-
return $http.get('/api/' + modelName + generateListQuery(options));
|
|
4972
|
+
return $http.get("/api/".concat(modelName), options);
|
|
4270
4973
|
},
|
|
4271
4974
|
deleteRecord: function (model, id) {
|
|
4272
|
-
return $http.delete(
|
|
4975
|
+
return $http.delete("/api/".concat(model, "/").concat(id));
|
|
4273
4976
|
},
|
|
4274
4977
|
updateRecord: function (modelName, id, dataToSave) {
|
|
4275
|
-
expCache.remove(
|
|
4276
|
-
return $http.post(
|
|
4978
|
+
expCache.remove("/api/".concat(modelName));
|
|
4979
|
+
return $http.post("/api/".concat(modelName, "/").concat(id), dataToSave);
|
|
4277
4980
|
},
|
|
4278
4981
|
createRecord: function (modelName, dataToSave) {
|
|
4279
|
-
expCache.remove(
|
|
4280
|
-
return $http.post(
|
|
4982
|
+
expCache.remove("/api/".concat(modelName));
|
|
4983
|
+
return $http.post("/api/".concat(modelName), dataToSave);
|
|
4281
4984
|
},
|
|
4282
4985
|
useCache: function (val) {
|
|
4283
4986
|
useCacheForGetAll = val;
|
|
@@ -4409,11 +5112,14 @@ var fng;
|
|
|
4409
5112
|
var controllers;
|
|
4410
5113
|
(function (controllers) {
|
|
4411
5114
|
/*@ngInject*/
|
|
4412
|
-
NavCtrl.$inject = ["$rootScope", "$window", "$scope", "$
|
|
4413
|
-
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) {
|
|
4414
5117
|
function clearContextMenu() {
|
|
4415
5118
|
$scope.items = [];
|
|
4416
5119
|
$scope.contextMenu = undefined;
|
|
5120
|
+
$scope.contextMenuId = undefined;
|
|
5121
|
+
$scope.contextMenuHidden = undefined;
|
|
5122
|
+
$scope.contextMenuDisabled = undefined;
|
|
4417
5123
|
}
|
|
4418
5124
|
$rootScope.navScope = $scope; // Lets plugins access menus
|
|
4419
5125
|
clearContextMenu();
|
|
@@ -4491,11 +5197,25 @@ var fng;
|
|
|
4491
5197
|
}
|
|
4492
5198
|
return result;
|
|
4493
5199
|
};
|
|
5200
|
+
function initialiseContextMenu(menuCaption) {
|
|
5201
|
+
$scope.contextMenu = menuCaption;
|
|
5202
|
+
var menuId = "".concat(_.camelCase(menuCaption), "ContextMenu");
|
|
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
|
+
});
|
|
5213
|
+
}
|
|
4494
5214
|
$scope.$on('fngControllersLoaded', function (evt, sharedData, modelName) {
|
|
4495
|
-
|
|
5215
|
+
initialiseContextMenu(sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false));
|
|
4496
5216
|
if (sharedData.dropDownDisplayPromise) {
|
|
4497
5217
|
sharedData.dropDownDisplayPromise.then(function (value) {
|
|
4498
|
-
|
|
5218
|
+
initialiseContextMenu(value);
|
|
4499
5219
|
});
|
|
4500
5220
|
}
|
|
4501
5221
|
});
|
|
@@ -4544,8 +5264,9 @@ var fng;
|
|
|
4544
5264
|
return item.isHidden ? item.isHidden() : false;
|
|
4545
5265
|
}
|
|
4546
5266
|
var dividerHide = false;
|
|
5267
|
+
var item = $scope.items[index];
|
|
4547
5268
|
// Hide a divider if it appears under another
|
|
4548
|
-
if (
|
|
5269
|
+
if (item.divider) {
|
|
4549
5270
|
if (index === 0) {
|
|
4550
5271
|
dividerHide = true;
|
|
4551
5272
|
}
|
|
@@ -4565,7 +5286,7 @@ var fng;
|
|
|
4565
5286
|
}
|
|
4566
5287
|
}
|
|
4567
5288
|
}
|
|
4568
|
-
return dividerHide || explicitlyHidden(
|
|
5289
|
+
return dividerHide || explicitlyHidden(item);
|
|
4569
5290
|
};
|
|
4570
5291
|
$scope.isDisabled = function (index) {
|
|
4571
5292
|
return $scope.items[index].isDisabled ? $scope.items[index].isDisabled() : false;
|
|
@@ -4651,7 +5372,8 @@ var fng;
|
|
|
4651
5372
|
.factory('pluginHelper', fng.services.pluginHelper)
|
|
4652
5373
|
.factory('recordHandler', fng.services.recordHandler)
|
|
4653
5374
|
.factory('SchemasService', fng.services.SchemasService)
|
|
4654
|
-
.factory('SubmissionsService', fng.services.SubmissionsService)
|
|
5375
|
+
.factory('SubmissionsService', fng.services.SubmissionsService)
|
|
5376
|
+
.factory('securityService', fng.services.securityService);
|
|
4655
5377
|
})(fng || (fng = {}));
|
|
4656
5378
|
// expose the library
|
|
4657
5379
|
var formsAngular = fng.formsAngular;
|