forms-angular 0.12.0-beta.191 → 0.12.0-beta.193

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.
@@ -5,21 +5,52 @@ var fng;
5
5
  (function (directives) {
6
6
  /*@ngInject*/
7
7
  function modelControllerDropdown() {
8
+ var menuVisibilityStr;
9
+ var menuDisabledStr;
10
+ var itemVisibilityStr = "isHidden($index)";
11
+ var itemClassStr = "ng-class=\"dropdownClass($index)\"";
12
+ if (fng.formsAngular.elemSecurityFuncBinding) {
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
+ // as ng-class is already being used, we'll add the .disabled class if the menu item is securely disabled using
18
+ // class="{{ }}". note that we "prevent" a disabled menu item from being clicked by checking for the DISABLED
19
+ // attribute in the doClick(...) function, and aborting if this is found.
20
+ // note that the 'normal' class introduced here might not actually do anything, but for one-time binding to work
21
+ // properly, we need a truthy value
22
+ itemClassStr += " class=\"{{ ".concat(bindingStr, "isSecurelyDisabled(choice.id) ? 'disabled' : 'normal' }}\"");
23
+ if (oneTimeBinding) {
24
+ // because the isHidden(...) logic is highly likely to be model dependent, that cannot be one-time bound. to
25
+ // be able to combine one-time and regular binding, we'll use ng-if for the one-time bound stuff and ng-hide for the rest
26
+ itemVisibilityStr = "ng-if=\"::!isSecurelyHidden(choice.id)\" ng-hide=\"".concat(itemVisibilityStr, "\"");
27
+ }
28
+ else if (fng.formsAngular.elemSecurityFuncBinding === "normal") {
29
+ itemVisibilityStr = "ng-hide=\"isSecurelyHidden(choice.id) || ".concat(itemVisibilityStr, "\"");
30
+ }
31
+ menuVisibilityStr = "ng-if=\"!contextMenuHidden\"";
32
+ menuDisabledStr = "disableable-link ng-disabled=\"contextMenuDisabled\"";
33
+ }
34
+ else {
35
+ menuVisibilityStr = "";
36
+ menuDisabledStr = "";
37
+ itemVisibilityStr = "ng-hide=\"".concat(itemVisibilityStr, "\"");
38
+ }
8
39
  return {
9
- restrict: 'AE',
40
+ restrict: "AE",
10
41
  replace: true,
11
- template: '<li ng-show="items.length > 0" class="mcdd" uib-dropdown>' +
42
+ template: "<li id=\"{{ contextMenuId }}\" ng-show=\"items.length > 0\" class=\"mcdd\" uib-dropdown ".concat(menuVisibilityStr, " ").concat(menuDisabledStr, ">") +
12
43
  ' <a href="#" uib-dropdown-toggle>' +
13
44
  ' {{contextMenu}} <b class="caret"></b>' +
14
- ' </a>' +
45
+ " </a>" +
15
46
  ' <ul class="uib-dropdown-menu dropdown-menu">' +
16
- ' <li ng-repeat="choice in items" ng-hide="isHidden($index)" ng-class="dropdownClass($index)">' +
47
+ " <li ng-repeat=\"choice in items\" ng-attr-id=\"{{choice.id}}\" ".concat(itemVisibilityStr, " ").concat(itemClassStr, ">") +
17
48
  ' <a ng-show="choice.text || choice.textFunc" class="dropdown-option" ng-href="{{choice.url || choice.urlFunc()}}" ng-click="doClick($index, $event)">' +
18
- ' {{ choice.text || choice.textFunc() }}' +
19
- ' </a>' +
20
- ' </li>' +
21
- ' </ul>' +
22
- '</li>'
49
+ " {{ choice.text || choice.textFunc() }}" +
50
+ " </a>" +
51
+ " </li>" +
52
+ " </ul>" +
53
+ "</li>",
23
54
  };
24
55
  }
25
56
  directives.modelControllerDropdown = modelControllerDropdown;
@@ -288,7 +319,7 @@ var fng;
288
319
  // <input type="text" class="input-small" placeholder="Email">
289
320
  var subkeys = [];
290
321
  var tabsSetup = tabsSetupState.N;
291
- var generateInput = function (fieldInfo, modelString, isRequired, idString, options) {
322
+ var generateInput = function (fieldInfo, modelString, isRequired, options) {
292
323
  function generateEnumInstructions() {
293
324
  var enumInstruction;
294
325
  if (angular.isArray(scope[fieldInfo.options])) {
@@ -314,6 +345,10 @@ var fng;
314
345
  }
315
346
  return enumInstruction;
316
347
  }
348
+ var idString = fieldInfo.id;
349
+ if (fieldInfo.array || options.subschema) {
350
+ idString = formMarkupHelper.generateArrayElementIdString(idString, fieldInfo, options);
351
+ }
317
352
  var nameString;
318
353
  if (!modelString) {
319
354
  var modelBase = (options.model || 'record') + '.';
@@ -335,7 +370,6 @@ var fng;
335
370
  }
336
371
  else {
337
372
  modelString += '[$index].' + lastPart;
338
- idString = null;
339
373
  nameString = compoundName.replace(/\./g, '-');
340
374
  }
341
375
  }
@@ -350,28 +384,13 @@ var fng;
350
384
  isRequired = isRequired || fieldInfo.required;
351
385
  var requiredStr = isRequired ? ' required ' : '';
352
386
  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
387
  switch (fieldInfo.type) {
369
388
  case 'select':
370
389
  if (fieldInfo.select2) {
371
390
  value = '<input placeholder="fng-select2 has been removed" readonly>';
372
391
  }
373
392
  else {
374
- common += handleReadOnlyDisabled(fieldInfo.readonly);
393
+ common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo);
375
394
  common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
376
395
  common += " aria-label=\"".concat(fieldInfo.label && fieldInfo.label !== "" ? fieldInfo.label : fieldInfo.name, "\" ");
377
396
  value = '<select ' + common + 'class="' + allInputsVars.formControl.trim() + allInputsVars.compactClass + allInputsVars.sizeClassBS2 + '" ' + requiredStr + '>';
@@ -425,7 +444,7 @@ var fng;
425
444
  case 'radio':
426
445
  value = '';
427
446
  common += requiredStr;
428
- common += handleReadOnlyDisabled(fieldInfo.readonly);
447
+ common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo);
429
448
  common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
430
449
  var separateLines = options.formstyle === 'vertical' || (options.formstyle !== 'inline' && !fieldInfo.inlineRadio);
431
450
  if (angular.isArray(fieldInfo.options)) {
@@ -456,7 +475,7 @@ var fng;
456
475
  break;
457
476
  case 'checkbox':
458
477
  common += requiredStr;
459
- common += handleReadOnlyDisabled(fieldInfo.readonly);
478
+ common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo);
460
479
  common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
461
480
  value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
462
481
  if (cssFrameworkService.framework() === 'bs3') {
@@ -465,6 +484,7 @@ var fng;
465
484
  break;
466
485
  default:
467
486
  common += formMarkupHelper.addTextInputMarkup(allInputsVars, fieldInfo, requiredStr);
487
+ common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo);
468
488
  if (fieldInfo.type === 'textarea') {
469
489
  if (fieldInfo.rows) {
470
490
  if (fieldInfo.rows === 'auto') {
@@ -629,6 +649,9 @@ var fng;
629
649
  };
630
650
  var handleField = function (info, options) {
631
651
  var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
652
+ if (fieldChrome.omit) {
653
+ return "";
654
+ }
632
655
  var template = fieldChrome.template;
633
656
  if (info.schema) {
634
657
  var niceName = info.name.replace(/\./g, '_');
@@ -685,12 +708,24 @@ var fng;
685
708
  if (info.formStyle === "inline" && info.inlineHeaders) {
686
709
  template += generateInlineHeaders(info.schema, options, model, info.inlineHeaders === "always");
687
710
  }
688
- template += '<ol class="sub-doc"' + (info.sortable ? " ui-sortable=\"sortableOptions\" ng-model=\"".concat(model, "\"") : '') + '>';
689
- template += '<li ng-form class="' + (cssFrameworkService.framework() === 'bs2' ? 'row-fluid ' : '') +
690
- (info.inlineHeaders ? 'width-controlled ' : '') +
691
- convertFormStyleToClass(info.formStyle) + ' ' + (info.ngClass ? "ng-class:" + info.ngClass : "") + '" name="form_' + niceName + '{{$index}}" class="sub-doc well" id="' + info.id + 'List_{{$index}}" ' +
692
- ' ng-repeat="subDoc in ' + model + ' track by $index"' +
693
- (info.filterable ? ' data-ng-hide="subDoc._hidden"' : "") + '>';
711
+ var disableCond = formMarkupHelper.handleReadOnlyDisabled(info);
712
+ // if we already know that the field is disabled (only possible when formsAngular.elemSecurityFuncBinding === "instant")
713
+ // then we don't need to add the sortable attribute at all
714
+ // otherwise, we need to include the ng-disabled on the <ol> so this can be referenced by the ui-sortable directive
715
+ // (see sortableOptions)
716
+ var sortableStr = info.sortable && disableCond.trim().toLowerCase() !== "disabled"
717
+ ? "".concat(disableCond, " ui-sortable=\"sortableOptions\" ng-model=\"").concat(model, "\"")
718
+ : "";
719
+ template += "<ol class=\"sub-doc\" ".concat(sortableStr, ">");
720
+ template +=
721
+ "<li ng-form id=\"".concat(info.id, "List_{{$index}}\" name=\"form_").concat(niceName, "{{$index}}\" ") +
722
+ " class=\"".concat(convertFormStyleToClass(info.formStyle)) +
723
+ " ".concat(cssFrameworkService.framework() === 'bs2' ? 'row-fluid' : '') +
724
+ " ".concat(info.inlineHeaders ? 'width-controlled' : '') +
725
+ " ".concat(info.ngClass ? "ng-class:" + info.ngClass : '', "\"") +
726
+ " ng-repeat=\"subDoc in ".concat(model, " track by $index\"") +
727
+ " ".concat(info.filterable ? 'data-ng-hide="subDoc._hidden"' : '') +
728
+ ">";
694
729
  if (cssFrameworkService.framework() === 'bs2') {
695
730
  template += '<div class="row-fluid sub-doc">';
696
731
  }
@@ -700,7 +735,7 @@ var fng;
700
735
  template += info.customSubDoc;
701
736
  }
702
737
  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)\"");
738
+ 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
739
  if (info.remove) {
705
740
  template += ' class="remove-btn btn btn-mini btn-default btn-xs form-btn"><i class="' + formMarkupHelper.glyphClass() + '-minus"></i> Remove';
706
741
  }
@@ -755,7 +790,11 @@ var fng;
755
790
  else {
756
791
  hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, "\"") : '';
757
792
  indicatorShowCond = info.noAdd ? "ng-show=\"".concat(info.noAdd, " && ").concat(indicatorShowCond, "\"") : '';
758
- footer += "<button ".concat(hideCond, " id=\"add_").concat(info.id, "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('").concat(info.name, "',$event)\">\n <i class=\"' + formMarkupHelper.glyphClass() + '-plus\"></i> \n Add\n </button>");
793
+ var disableCond_1 = formMarkupHelper.handleReadOnlyDisabled(info);
794
+ footer +=
795
+ "<button ".concat(hideCond, " ").concat(disableCond_1, " id=\"add_").concat(info.id, "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('").concat(info.name, "',$event)\">") +
796
+ " <i class=\"".concat(formMarkupHelper.glyphClass(), "-plus\"></i> Add ") +
797
+ "</button>";
759
798
  }
760
799
  if (info.noneIndicator) {
761
800
  footer += "<span ".concat(indicatorShowCond, " class=\"none_indicator\" id=\"no_").concat(info.id, "_indicator\">None</span>");
@@ -790,12 +829,16 @@ var fng;
790
829
  throw new Error('Cannot use arrays in an inline or stacked form');
791
830
  }
792
831
  template += formMarkupHelper.label(scope, info, info.type !== 'link', options);
793
- template += formMarkupHelper.handleArrayInputAndControlDiv(generateInput(info, info.type === 'link' ? null : 'arrayItem.x', true, info.id + '_{{$index}}', options), controlDivClasses, info, options);
832
+ var stashedHelp = info.help;
833
+ delete info.help;
834
+ var inputHtml = generateInput(info, info.type === 'link' ? null : 'arrayItem.x', true, options);
835
+ info.help = stashedHelp;
836
+ template += formMarkupHelper.handleArrayInputAndControlDiv(inputHtml, controlDivClasses, info, options);
794
837
  }
795
838
  else {
796
839
  // Single fields here
797
840
  template += formMarkupHelper.label(scope, info, null, options);
798
- template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, info.id, options), controlDivClasses);
841
+ template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, options), controlDivClasses);
799
842
  }
800
843
  }
801
844
  template += fieldChrome.closeTag;
@@ -1950,7 +1993,7 @@ var fng;
1950
1993
  angular.extend(formInstructions, mongooseType.options.form);
1951
1994
  }
1952
1995
  }
1953
- if (mongooseType.instance === 'String') {
1996
+ if (mongooseType.instance === 'String' || (mongooseType.instance === 'ObjectID' && formInstructions.asText)) {
1954
1997
  if (mongooseOptions.enum) {
1955
1998
  formInstructions.type = formInstructions.type || 'select';
1956
1999
  if (formInstructions.select2) {
@@ -2261,14 +2304,19 @@ var fng;
2261
2304
  return forceNextTime;
2262
2305
  },
2263
2306
  add: function add(fieldName, $event, $scope, modelOverride) {
2264
- var _a;
2307
+ var _a, _b;
2308
+ // for buttons, the click event won't fire if the disabled attribute exists, but the same is not true of
2309
+ // icons, so we need to check this for simple array item addition
2310
+ if (((_a = $event === null || $event === void 0 ? void 0 : $event.target) === null || _a === void 0 ? void 0 : _a.hasAttribute) && $event.target.hasAttribute("disabled")) {
2311
+ return $event.preventDefault();
2312
+ }
2265
2313
  // check that target element is visible. May not be reliable - see https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
2266
2314
  if ($event.target.offsetParent) {
2267
2315
  var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2268
2316
  var schemaElement = $scope.formSchema.find(function (f) { return f.name === fieldName; }); // In case someone is using the formSchema directly
2269
2317
  var subSchema = schemaElement ? schemaElement.schema : null;
2270
2318
  var obj = subSchema ? $scope.setDefaults(subSchema, fieldName + '.') : {};
2271
- if (typeof ((_a = $scope.dataEventFunctions) === null || _a === void 0 ? void 0 : _a.onInitialiseNewSubDoc) === "function") {
2319
+ if (typeof ((_b = $scope.dataEventFunctions) === null || _b === void 0 ? void 0 : _b.onInitialiseNewSubDoc) === "function") {
2272
2320
  $scope.dataEventFunctions.onInitialiseNewSubDoc(fieldName, subSchema, obj);
2273
2321
  }
2274
2322
  arrayField.push(obj);
@@ -2281,6 +2329,12 @@ var fng;
2281
2329
  $scope.setFormDirty($event);
2282
2330
  },
2283
2331
  remove: function remove(fieldName, value, $event, $scope, modelOverride) {
2332
+ var _a;
2333
+ // for buttons, the click event won't fire if the disabled attribute exists, but the same is not true of
2334
+ // icons, so we need to check this for simple array item removal
2335
+ if (((_a = $event === null || $event === void 0 ? void 0 : $event.target) === null || _a === void 0 ? void 0 : _a.hasAttribute) && $event.target.hasAttribute("disabled")) {
2336
+ return $event.preventDefault();
2337
+ }
2284
2338
  // Remove an element from an array
2285
2339
  var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2286
2340
  var err;
@@ -2415,8 +2469,8 @@ var fng;
2415
2469
  var services;
2416
2470
  (function (services) {
2417
2471
  /*@ngInject*/
2418
- formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "$filter"];
2419
- function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, $filter) {
2472
+ formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "$filter", "$rootScope"];
2473
+ function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, $filter, $rootScope) {
2420
2474
  function generateNgShow(showWhen, model) {
2421
2475
  function evaluateSide(side) {
2422
2476
  var result = side;
@@ -2454,25 +2508,108 @@ var fng;
2454
2508
  function glyphClass() {
2455
2509
  return (cssFrameworkService.framework() === 'bs2' ? 'icon' : 'glyphicon glyphicon');
2456
2510
  }
2511
+ function handleReadOnlyDisabled(fieldInfo) {
2512
+ if (fieldInfo.readonly && typeof fieldInfo.readonly === "boolean") {
2513
+ // if we have a true-valued readonly property then this trumps whatever security rule might apply to this field
2514
+ return " disabled ";
2515
+ }
2516
+ function wrapReadOnly() {
2517
+ return fieldInfo.readonly ? " ng-disabled=\"".concat(fieldInfo.readonly, "\" ") : "";
2518
+ }
2519
+ if (!fieldInfo.id || !fng.formsAngular.elemSecurityFuncBinding || !fng.formsAngular.elemSecurityFuncName || !$rootScope[fng.formsAngular.elemSecurityFuncName]) {
2520
+ // no security, so we're just concerned about what value fieldInfo.readonly has
2521
+ return wrapReadOnly();
2522
+ }
2523
+ if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
2524
+ // "instant" security is evaluated now, and a positive result trumps whatever fieldInfo.readonly might be set to
2525
+ if ($rootScope.isSecurelyDisabled(fieldInfo.id)) {
2526
+ return " disabled ";
2527
+ }
2528
+ else {
2529
+ return wrapReadOnly();
2530
+ }
2531
+ }
2532
+ var securityFuncStr = "".concat(fng.formsAngular.elemSecurityFuncName, "('").concat(fieldInfo.id, "', 'disabled')");
2533
+ var oneTimeBinding = fng.formsAngular.elemSecurityFuncBinding === "one-time";
2534
+ if (fieldInfo.readonly) {
2535
+ // we have both security and a read-only attribute to deal with
2536
+ if (oneTimeBinding) {
2537
+ // if our field has a string-typed readonly attribute *and* one-time binding is required by our securityFunc, we
2538
+ // cannot simply combine these into a single ng-disabled expression, because the readonly property is highly
2539
+ // likely to be model-dependent and therefore cannot use one-time-binding. the best we can do in this case is
2540
+ // to use ng-disabled for the field's readonly property, and a one-time-bound ng-readonly for the securityFunc.
2541
+ // this is not perfect, because in the case of selects, ng-readonly doesn't actually prevent the user from
2542
+ // making a selection. however, the select will be styled as if it is disabled (including the not-allowed
2543
+ // cursor), which should deter the user in most cases.
2544
+ return wrapReadOnly() + "ng-readonly=\"::".concat(securityFuncStr, "\" ");
2545
+ }
2546
+ else {
2547
+ // if we have both things and we are *NOT* required to use one-time binding for the securityFunc, then they can
2548
+ // simply be combined into a single ng-disabled expression
2549
+ return " ng-disabled=\"".concat(securityFuncStr, " || ").concat(fieldInfo.readonly, "\" ");
2550
+ }
2551
+ }
2552
+ else {
2553
+ // we have security only
2554
+ return " ng-disabled=\"".concat(oneTimeBinding ? "::" : "").concat(securityFuncStr, "\" ");
2555
+ }
2556
+ }
2557
+ function generateArrayElementIdString(idString, info, options) {
2558
+ if (options.subschema && options.model) {
2559
+ // for subschemas, it is possible that our model will begin with $parent., or $parent.$parent. (etc). though a bit of
2560
+ // a hack where this does occur (probably where a directive used by a sub-schema is using a nested <form-input>
2561
+ // directive), we need to look for the $index in the same place as our model is looking for data.
2562
+ var model = options.model;
2563
+ var nestedSteps = 0;
2564
+ var stepIndicator = "$parent.";
2565
+ while (model.startsWith(stepIndicator)) {
2566
+ nestedSteps++;
2567
+ model = model.substring(stepIndicator.length);
2568
+ }
2569
+ return "".concat(idString, "_{{").concat(stepIndicator.repeat(nestedSteps), "$index}}");
2570
+ }
2571
+ else {
2572
+ return "".concat(idString, "_{{$index}}");
2573
+ }
2574
+ }
2575
+ function isArrayElement(scope, info, options) {
2576
+ return scope["$index"] !== undefined || !!options.subschema;
2577
+ }
2457
2578
  return {
2458
2579
  isHorizontalStyle: isHorizontalStyle,
2580
+ isArrayElement: isArrayElement,
2459
2581
  fieldChrome: function fieldChrome(scope, info, options) {
2582
+ var insert = '';
2583
+ if (info.id && typeof info.id.replace === "function") {
2584
+ var idStr = "cg_".concat(info.id.replace(/\./g, '-'));
2585
+ insert += "id=\"".concat(isArrayElement(scope, info, options) ? generateArrayElementIdString(idStr, info, options) : idStr, "\"");
2586
+ if (fng.formsAngular.elemSecurityFuncBinding && fng.formsAngular.elemSecurityFuncName && $rootScope[fng.formsAngular.elemSecurityFuncName]) {
2587
+ if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
2588
+ if ($rootScope.isSecurelyHidden(idStr)) {
2589
+ // if our securityFunc supports instant binding and evaluates to true, then nothing needs to be
2590
+ // added to the dom for this field, which we indicate to our caller as follows...
2591
+ return { omit: true };
2592
+ }
2593
+ ;
2594
+ }
2595
+ else {
2596
+ var bindingStr = fng.formsAngular.elemSecurityFuncBinding === "one-time" ? "::" : "";
2597
+ insert += " data-ng-if=\"".concat(bindingStr, "!").concat(fng.formsAngular.elemSecurityFuncName, "('").concat(idStr, "', 'hidden')\"");
2598
+ }
2599
+ }
2600
+ }
2460
2601
  var classes = info.classes || '';
2461
2602
  var template = '';
2462
2603
  var closeTag = '';
2463
- var insert = '';
2464
2604
  info.showWhen = info.showWhen || info.showwhen; // deal with use within a directive
2465
2605
  if (info.showWhen) {
2466
2606
  if (typeof info.showWhen === 'string') {
2467
- insert += 'ng-show="' + info.showWhen + '"';
2607
+ insert += ' ng-show="' + info.showWhen + '"';
2468
2608
  }
2469
2609
  else {
2470
- insert += 'ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
2610
+ insert += ' ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
2471
2611
  }
2472
2612
  }
2473
- if (info.id && typeof info.id.replace === "function") {
2474
- insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
2475
- }
2476
2613
  if (cssFrameworkService.framework() === 'bs3') {
2477
2614
  classes += ' form-group';
2478
2615
  if (options.formstyle === 'vertical' && info.size !== 'block-level') {
@@ -2532,7 +2669,8 @@ var fng;
2532
2669
  }
2533
2670
  labelHTML += addAllService.addAll(scope, 'Label', null, options) + ' class="' + classes + '">' + fieldInfo.label;
2534
2671
  if (addButtonMarkup) {
2535
- labelHTML += ' <i id="add_' + fieldInfo.id + '" ng-click="add(\'' + fieldInfo.name + '\',$event)" class="' + glyphClass() + '-plus-sign"></i>';
2672
+ var disableCond = handleReadOnlyDisabled(fieldInfo);
2673
+ labelHTML += " <i ".concat(disableCond, " id=\"add_").concat(fieldInfo.id, "\" ng-click=\"add('").concat(fieldInfo.name, "', $event)\" class=\"").concat(glyphClass(), "-plus-sign\"></i>");
2536
2674
  }
2537
2675
  labelHTML += '</label>';
2538
2676
  if (fieldInfo.linklabel) {
@@ -2568,13 +2706,24 @@ var fng;
2568
2706
  if (['inline', 'stacked'].includes(options.formstyle)) {
2569
2707
  placeHolder = placeHolder || fieldInfo.label;
2570
2708
  }
2571
- common = 'data-ng-model="' + modelString + '"' + (idString ? ' id="' + idString + '" name="' + idString + '" ' : ' name="' + nameString + '" ');
2572
- common += (placeHolder ? ('placeholder="' + placeHolder + '" ') : '');
2709
+ common = 'data-ng-model="' + modelString + '"';
2710
+ if (idString) {
2711
+ common += " id=\"".concat(idString, "\"");
2712
+ }
2713
+ if (nameString) {
2714
+ common += " name=\"".concat(nameString, "\"");
2715
+ }
2716
+ else if (idString) {
2717
+ common += " name=\"".concat(idString, "\"");
2718
+ }
2719
+ if (placeHolder) {
2720
+ common += " placeholder=\"".concat(placeHolder, "\"");
2721
+ }
2573
2722
  if (fieldInfo.popup) {
2574
- common += 'title="' + fieldInfo.popup + '" ';
2723
+ common += " title=\"".concat(fieldInfo.popup, "\"");
2575
2724
  }
2576
2725
  if (fieldInfo.ariaLabel) {
2577
- common += 'aria-label="' + fieldInfo.ariaLabel + '" ';
2726
+ common += " aria-label=\"".concat(fieldInfo.ariaLabel, "\"");
2578
2727
  }
2579
2728
  common += addAllService.addAll(scope, 'Field', null, options);
2580
2729
  return {
@@ -2595,6 +2744,9 @@ var fng;
2595
2744
  var helpMarkup = cssFrameworkService.framework() === 'bs2' ? { el: 'span', cl: 'help-inline' } : { el: 'div', cl: 'help-block' };
2596
2745
  value += "<".concat(helpMarkup.el, " class=\"").concat(helpMarkup.cl, "\">").concat(inlineHelp, "</").concat(helpMarkup.el, ">");
2597
2746
  }
2747
+ // this is a dummy tag identifying where the input ends and the messages block (that is only visible when the form field is $dirty)
2748
+ // begins. our caller could replace this tag with anything it needs to insert between these two things.
2749
+ value += "<dms/>";
2598
2750
  if (!options.noid) {
2599
2751
  value += "<div ng-if=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$dirty\" class=\"help-block\">") +
2600
2752
  " <div ng-messages=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$error\">") +
@@ -2636,17 +2788,20 @@ var fng;
2636
2788
  return inputMarkup;
2637
2789
  },
2638
2790
  handleArrayInputAndControlDiv: function handleArrayInputAndControlDiv(inputMarkup, controlDivClasses, info, options) {
2639
- var result = '<div ';
2640
- if (cssFrameworkService.framework() === 'bs3') {
2641
- result += 'ng-class="skipCols($index)" ';
2642
- }
2643
- result += 'class="' + controlDivClasses.join(' ') + '" id="' + info.id + 'List" ';
2644
- result += 'ng-repeat="arrayItem in ' + (options.model || 'record') + '.' + info.name + ' track by $index">';
2645
- result += inputMarkup;
2646
- if (info.type !== 'link') {
2647
- result += '<i ng-click="remove(\'' + info.name + '\',$index,$event)" id="remove_' + info.id + '_{{$index}}" class="' + glyphClass() + '-minus-sign"></i>';
2648
- }
2791
+ var indentStr = cssFrameworkService.framework() === 'bs3' ? 'ng-class="skipCols($index)" ' : "";
2792
+ var arrayStr = (options.model || 'record') + '.' + info.name;
2793
+ var result = "";
2794
+ result += '<div id="' + info.id + 'List" class="' + controlDivClasses.join(' ') + '" ' + indentStr + ' ng-repeat="arrayItem in ' + arrayStr + ' track by $index">';
2795
+ var disableCond = handleReadOnlyDisabled(info);
2796
+ var removeBtn = info.type !== 'link'
2797
+ ? "<i ".concat(disableCond, " ng-click=\"remove('").concat(info.name, "', $index, $event)\" id=\"remove_").concat(info.id, "_{{$index}}\" class=\"").concat(glyphClass(), "-minus-sign\"></i>")
2798
+ : "";
2799
+ result += inputMarkup.replace("<dms/>", removeBtn);
2649
2800
  result += '</div>';
2801
+ indentStr = cssFrameworkService.framework() === 'bs3' ? 'ng-class="skipCols(' + arrayStr + '.length)" ' : "";
2802
+ if (info.help) {
2803
+ result += '<div class="array-help-block ' + controlDivClasses.join(' ') + '" ' + indentStr + ' id="empty' + info.id + 'ListHelpBlock">' + info.help + '</div>';
2804
+ }
2650
2805
  return result;
2651
2806
  },
2652
2807
  addTextInputMarkup: function addTextInputMarkup(allInputsVars, fieldInfo, requiredStr) {
@@ -2659,14 +2814,10 @@ var fng;
2659
2814
  result += ' ' + fieldInfo.add + ' ';
2660
2815
  }
2661
2816
  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
2817
  return result;
2669
- }
2818
+ },
2819
+ handleReadOnlyDisabled: handleReadOnlyDisabled,
2820
+ generateArrayElementIdString: generateArrayElementIdString
2670
2821
  };
2671
2822
  }
2672
2823
  services.formMarkupHelper = formMarkupHelper;
@@ -2705,106 +2856,187 @@ var fng;
2705
2856
  /*@ngInject*/
2706
2857
  pluginHelper.$inject = ["formMarkupHelper"];
2707
2858
  function pluginHelper(formMarkupHelper) {
2708
- return {
2709
- extractFromAttr: function extractFromAttr(attr, directiveName) {
2710
- function deserialize(str) {
2711
- var retVal = str.replace(/&quot;/g, '"');
2712
- if (retVal === 'true') {
2713
- retVal = true;
2859
+ function internalGenDisabledStr(id, processedAttrs, forceNg) {
2860
+ var result = formMarkupHelper.handleReadOnlyDisabled({ id: id, readonly: processedAttrs.info.readonly }).trim();
2861
+ // some types of control (such as ui-select) don't deal correctly with a DISABLED attribute and
2862
+ // need ng-disabled, even when the expression is simply "true"
2863
+ if (forceNg && result.toLowerCase() === "disabled") {
2864
+ return 'ng-disabled="true"';
2865
+ }
2866
+ else {
2867
+ return result;
2868
+ }
2869
+ }
2870
+ function makeIdStringUniqueForArrayElements(scope, processedAttrs, idString) {
2871
+ if (formMarkupHelper.isArrayElement(scope, processedAttrs.info, processedAttrs.options)) {
2872
+ return formMarkupHelper.generateArrayElementIdString(idString, processedAttrs.info, processedAttrs.options);
2873
+ }
2874
+ else {
2875
+ return idString;
2876
+ }
2877
+ }
2878
+ function internalGenIdString(scope, processedAttrs, suffix, makeUniqueForArrayElements) {
2879
+ var result = processedAttrs.info.id;
2880
+ if (suffix) {
2881
+ if (!suffix.startsWith("_")) {
2882
+ result += "_";
2883
+ }
2884
+ result += suffix;
2885
+ }
2886
+ if (makeUniqueForArrayElements) {
2887
+ result = makeIdStringUniqueForArrayElements(scope, processedAttrs, result);
2888
+ }
2889
+ return result;
2890
+ }
2891
+ function internalGenDateTimePickerDisabledStr(processedAttrs, idString) {
2892
+ var rawDisabledStr = internalGenDisabledStr(idString, processedAttrs, true);
2893
+ var disabledStr = "";
2894
+ // disabledStr might now include an ng-disabled attribute. To disable both the date and time inputs, we need to
2895
+ // take the value of that attribute and wrap it up as two new attributes: "disabledDate" and "readonlyTime"
2896
+ // (which is what the datetimepicker directive is expecting to receive)
2897
+ if (rawDisabledStr) {
2898
+ // disabledStr should contain either 'ng-disabled="xxxx"' or 'ng-readonly="yyyy"', or both.
2899
+ // the values of xxxx and yyyy could be more-or-less anything, and certainly they could include = or ", which
2900
+ // makes parsing hard
2901
+ // our strategy will be to re-format disabledStr as if it was the string representation of an object, and
2902
+ // then parse it. we can then refer to the ng-disabled and ng-readonly attributes of the parsed object.
2903
+ // in the future, perhaps ng-disabled and ng-readonly will be changed to data-ng-disabled and data-ng-readonly
2904
+ rawDisabledStr = rawDisabledStr.replace("data-ng-disabled", "ng-disabled");
2905
+ rawDisabledStr = rawDisabledStr.replace("data-ng-readonly", "ng-readonly");
2906
+ rawDisabledStr = rawDisabledStr.replace("ng-disabled=", '"ng-disabled":');
2907
+ rawDisabledStr = rawDisabledStr.replace("ng-readonly=", '"ng-readonly":');
2908
+ try {
2909
+ rawDisabledStr = "{ ".concat(rawDisabledStr, " }");
2910
+ var disabledObj = JSON.parse(rawDisabledStr);
2911
+ rawDisabledStr = disabledObj["ng-disabled"];
2912
+ // cannot see a way to sensibly deal with both ng-disabled and ng-readonly. Let's just ignore the ng-readonly
2913
+ // for now - with the way handleReadOnlyDisabled is currently written, this means we'll be unable to fully
2914
+ // support a datetime field with a string-typed "readonly" attribute and where fngAngular's elemSecurityFuncBinding
2915
+ // option is set up to "one-time" or "normal".
2916
+ if (rawDisabledStr) {
2917
+ disabledStr = "disabledDate=\"".concat(rawDisabledStr, "\" readonlyTime=\"").concat(rawDisabledStr, "\"");
2714
2918
  }
2715
- else if (retVal === 'false') {
2716
- retVal = false;
2919
+ }
2920
+ catch (e) {
2921
+ // give up
2922
+ }
2923
+ }
2924
+ return disabledStr;
2925
+ }
2926
+ function extractFromAttr(attr, directiveName) {
2927
+ function deserialize(str) {
2928
+ var retVal = str.replace(/&quot;/g, '"');
2929
+ if (retVal === "true") {
2930
+ return true;
2931
+ }
2932
+ else if (retVal === "false") {
2933
+ return false;
2934
+ }
2935
+ else {
2936
+ var num = parseFloat(retVal);
2937
+ if (!isNaN(num) && isFinite(num)) {
2938
+ return num;
2717
2939
  }
2718
- else if (!isNaN(parseFloat(retVal)) && isFinite(retVal)) {
2719
- retVal = parseFloat(retVal);
2940
+ else {
2941
+ return retVal;
2720
2942
  }
2721
- return retVal;
2722
2943
  }
2723
- var info = {};
2724
- var options = { formStyle: attr.formstyle };
2725
- var directiveOptions = {};
2726
- var directiveNameLength = directiveName ? directiveName.length : 0;
2727
- var lcDirectiveName = directiveName === null || directiveName === void 0 ? void 0 : directiveName.toLowerCase();
2728
- for (var prop in attr) {
2729
- if (attr.hasOwnProperty(prop)) {
2730
- var lcProp = prop.toLowerCase();
2731
- if (lcProp.slice(0, 6) === 'fngfld') {
2732
- info[lcProp.slice(6)] = deserialize(attr[prop]);
2733
- }
2734
- else if (lcProp.slice(0, 6) === 'fngopt') {
2735
- options[lcProp.slice(6)] = deserialize(attr[prop]);
2736
- }
2737
- else if (directiveName && lcProp.slice(0, directiveNameLength) === lcDirectiveName) {
2738
- directiveOptions[_.kebabCase(prop.slice(directiveNameLength))] = deserialize(attr[prop]);
2739
- }
2944
+ }
2945
+ var info = {};
2946
+ var options = { formStyle: attr.formstyle };
2947
+ var directiveOptions = {};
2948
+ var directiveNameLength = directiveName ? directiveName.length : 0;
2949
+ var lcDirectiveName = directiveName === null || directiveName === void 0 ? void 0 : directiveName.toLowerCase();
2950
+ for (var prop in attr) {
2951
+ if (attr.hasOwnProperty(prop)) {
2952
+ var lcProp = prop.toLowerCase();
2953
+ if (lcProp.slice(0, 6) === "fngfld") {
2954
+ info[lcProp.slice(6)] = deserialize(attr[prop]);
2955
+ }
2956
+ else if (lcProp.slice(0, 6) === "fngopt") {
2957
+ options[lcProp.slice(6)] = deserialize(attr[prop]);
2958
+ }
2959
+ else if (directiveName && lcProp.slice(0, directiveNameLength) === lcDirectiveName) {
2960
+ directiveOptions[_.kebabCase(prop.slice(directiveNameLength))] = deserialize(attr[prop]);
2740
2961
  }
2741
2962
  }
2742
- return { info: info, options: options, directiveOptions: directiveOptions };
2743
- },
2744
- buildInputMarkup: function buildInputMarkup(scope, model, info, options, addButtons, needsX, generateInputControl) {
2745
- var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options, ' id="cg_' + info.id + '"');
2746
- var controlDivClasses = formMarkupHelper.controlDivClasses(options);
2747
- var elementHtml = fieldChrome.template + formMarkupHelper.label(scope, info, addButtons, options);
2748
- var modelString, idString, nameString;
2749
- if (addButtons) {
2750
- modelString = 'arrayItem' + (needsX ? '.x' : '');
2751
- idString = info.id + '_{{$index}}';
2752
- nameString = info.name + '_{{$index}}';
2963
+ }
2964
+ return { info: info, options: options, directiveOptions: directiveOptions };
2965
+ }
2966
+ return {
2967
+ extractFromAttr: extractFromAttr,
2968
+ buildInputMarkup: function buildInputMarkup(scope, attrs, params, generateInputControl) {
2969
+ var processedAttrs = params.processedAttrs || extractFromAttr(attrs, "");
2970
+ var info = {};
2971
+ if (!params.ignoreFieldInfoFromAttrs) {
2972
+ Object.assign(info, processedAttrs.info);
2753
2973
  }
2754
- else {
2755
- modelString = model + '.' + info.name;
2756
- idString = info.id;
2757
- nameString = info.name;
2974
+ if (params.fieldInfoOverrides) {
2975
+ Object.assign(info, params.fieldInfoOverrides);
2758
2976
  }
2759
- if (options.subschema && info.name.indexOf('.') !== -1) {
2977
+ var options = Object.assign({}, processedAttrs.options, params.optionOverrides);
2978
+ var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
2979
+ if (fieldChrome.omit) {
2980
+ return "";
2981
+ }
2982
+ var controlDivClasses = formMarkupHelper.controlDivClasses(options);
2983
+ var elementHtml = fieldChrome.template + formMarkupHelper.label(scope, info, params.addButtons, options);
2984
+ var idString = info.id;
2985
+ if (info.array || options.subschema) {
2986
+ idString = formMarkupHelper.generateArrayElementIdString(idString, info, options);
2987
+ }
2988
+ var modelString = params.addButtons
2989
+ ? "arrayItem" + (params.needsX ? ".x" : "")
2990
+ : attrs.model + "." + info.name;
2991
+ var nameString = info.name;
2992
+ if (options.subschema && info.name.indexOf(".") !== -1) {
2760
2993
  // Schema handling - need to massage the ngModel and the id
2761
- var modelBase = model + '.';
2762
- var compoundName = info.name;
2994
+ var modelBase = attrs.model + ".";
2763
2995
  var root = options.subschemaroot;
2764
- var lastPart = compoundName.slice(root.length + 1);
2765
- modelString = modelBase;
2766
- if (options.index) {
2767
- modelString += root + '[' + options.index + '].' + lastPart;
2768
- idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|\]\.)/g, '-');
2996
+ var lastPart = info.name.slice(root.length + 1);
2997
+ modelString = modelBase + root;
2998
+ if (options.subkey) {
2999
+ idString = modelString.slice(modelBase.length).replace(/\./g, "-") + "-subkey" + options.subkeyno + "-" + lastPart;
3000
+ modelString += "[" + "$_arrayOffset_" + root.replace(/\./g, "_") + "_" + options.subkeyno + "]." + lastPart;
2769
3001
  }
2770
3002
  else {
2771
- modelString += root;
2772
- if (options.subkey) {
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
- }
3003
+ modelString += "[$index]." + lastPart;
3004
+ nameString = info.name.replace(/\./g, "-");
2781
3005
  }
2782
3006
  }
2783
3007
  var buildingBlocks = formMarkupHelper.allInputsVars(scope, info, options, modelString, idString, nameString);
2784
3008
  buildingBlocks.modelString = modelString;
2785
- elementHtml += formMarkupHelper['handle' + (addButtons ? 'Array' : '') + 'InputAndControlDiv'](formMarkupHelper.inputChrome(generateInputControl(buildingBlocks), info, options, buildingBlocks), controlDivClasses, info, options);
3009
+ // defer to the calling directive to generate the markup for the input(s)
3010
+ var inputHtml = generateInputControl(buildingBlocks);
3011
+ // wrap this in a div that puts it into the correct bootstrap 'column' and adds validation messages and help text
3012
+ var wrappedInputHtml = formMarkupHelper.inputChrome(inputHtml, info, options, buildingBlocks);
3013
+ // further wrap this to add the control div classes, and in the case of an array, the button that allows array elements to be removed
3014
+ var inputAndControlDivGenerator = params.addButtons
3015
+ ? formMarkupHelper.handleArrayInputAndControlDiv
3016
+ : formMarkupHelper.handleInputAndControlDiv;
3017
+ elementHtml += inputAndControlDivGenerator(wrappedInputHtml, controlDivClasses, info, options);
2786
3018
  elementHtml += fieldChrome.closeTag;
2787
3019
  return elementHtml;
2788
3020
  },
2789
- findIdInSchemaAndFlagNeedX: function findIdInSchemaAndFlagNeedX(scope, id) {
2790
- // Find the entry in the schema of scope for id and add a needsX property so string arrays are properly handled
2791
- var foundIt = false;
2792
- for (var i = 0; i < scope.length; i++) {
2793
- var element = scope[i];
2794
- if (element.id === id) {
2795
- element.needsX = true;
2796
- foundIt = true;
2797
- break;
2798
- }
2799
- else if (element.schema) {
2800
- if (findIdInSchemaAndFlagNeedX(element.schema, id)) {
2801
- foundIt = true;
2802
- break;
2803
- }
2804
- }
2805
- }
2806
- return foundIt;
2807
- }
3021
+ genIdString: function genIdString(scope, processedAttrs, idSuffix) {
3022
+ return internalGenIdString(scope, processedAttrs, idSuffix, true);
3023
+ },
3024
+ genDisabledStr: function genDisabledStr(scope, processedAttrs, idSuffix, forceNg) {
3025
+ var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
3026
+ return internalGenDisabledStr(idString, processedAttrs, forceNg);
3027
+ },
3028
+ genIdAndDisabledStr: function genIdAndDisabledStr(scope, processedAttrs, idSuffix, forceNg) {
3029
+ var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
3030
+ return "id=\"".concat(makeIdStringUniqueForArrayElements(scope, processedAttrs, idString), "\" ") + internalGenDisabledStr(idString, processedAttrs, forceNg);
3031
+ },
3032
+ genDateTimePickerDisabledStr: function genDateTimePickerDisabledStr(scope, processedAttrs, idSuffix) {
3033
+ var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
3034
+ return internalGenDateTimePickerDisabledStr(processedAttrs, idString);
3035
+ },
3036
+ genDateTimePickerIdAndDisabledStr: function genDateTimePickerIdAndDisabledStr(scope, processedAttrs, idSuffix) {
3037
+ var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
3038
+ return "id=\"".concat(makeIdStringUniqueForArrayElements(scope, processedAttrs, idString), "\" ") + internalGenDateTimePickerDisabledStr(processedAttrs, idString);
3039
+ },
2808
3040
  };
2809
3041
  }
2810
3042
  services.pluginHelper = pluginHelper;
@@ -3076,18 +3308,12 @@ var fng;
3076
3308
  var idList = $scope[suffixCleanId(schemaEntry, "_ids")];
3077
3309
  var thisConversion = void 0;
3078
3310
  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
3311
  if (
3083
- /*
3084
- Check we are starting with an ObjectId (ie not being called because of $watch on conversion, with a
3085
- converted value, which would cause an exception)
3086
- */
3087
- fieldValue.toString().match(/^[a-f0-9]{24}$/) &&
3088
- /*
3089
- We are not suppressing conversions
3090
- */
3312
+ // it's not a nested field
3313
+ !fieldName.includes(".") &&
3314
+ // 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)
3315
+ fieldValue.toString().match(/^[a-f0-9]{24}$/) &&
3316
+ // We are not suppressing conversions
3091
3317
  (!schemaEntry.internalRef || !schemaEntry.internalRef.noConvert)) {
3092
3318
  anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, "Options")], idList);
3093
3319
  }
@@ -3438,18 +3664,20 @@ var fng;
3438
3664
  return function (response) {
3439
3665
  if ([200, 400].indexOf(response.status) !== -1) {
3440
3666
  var errorMessage = "";
3441
- for (var errorField in response.data.errors) {
3442
- if (response.data.errors.hasOwnProperty(errorField)) {
3443
- errorMessage += "<li><b>" + $filter("titleCase")(errorField) + ": </b> ";
3444
- switch (response.data.errors[errorField].type) {
3445
- case "enum":
3446
- errorMessage += "You need to select from the list of values";
3447
- break;
3448
- default:
3449
- errorMessage += response.data.errors[errorField].message;
3450
- break;
3667
+ if (response.data && response.data.errors) {
3668
+ for (var errorField in response.data.errors) {
3669
+ if (response.data.errors.hasOwnProperty(errorField)) {
3670
+ errorMessage += "<li><b>" + $filter("titleCase")(errorField) + ": </b> ";
3671
+ switch (response.data.errors[errorField].type) {
3672
+ case "enum":
3673
+ errorMessage += "You need to select from the list of values";
3674
+ break;
3675
+ default:
3676
+ errorMessage += response.data.errors[errorField].message;
3677
+ break;
3678
+ }
3679
+ errorMessage += "</li>";
3451
3680
  }
3452
- errorMessage += "</li>";
3453
3681
  }
3454
3682
  }
3455
3683
  if (errorMessage.length > 0) {
@@ -4088,11 +4316,25 @@ var fng;
4088
4316
  //}
4089
4317
  };
4090
4318
  $scope.sortableOptions = {
4091
- update: function () {
4092
- if ($scope.topLevelFormName) {
4319
+ update: function (e, ui) {
4320
+ if (e.target.hasAttribute("disabled")) {
4321
+ // where formsAngular.elemSecurityFuncBinding is set to "one-time" or "normal", the <ol> that the
4322
+ // ui-sortable directive has been used with will have an ng-disabled that may or may not have caused
4323
+ // a disabled attribute to be added to that element. in the case where this attribute has been
4324
+ // added, sorting should be prevented.
4325
+ // allowing the user to begin the drag, and then preventing it only once they release the mouse button,
4326
+ // doesn't seem like the best solution, but I've yet to find something that works better. the
4327
+ // cancel property (see commented-out code below) looks like it should work (and kind of does), but this
4328
+ // screws up mouse events on input fields hosted within the draggable <li> items, so you're
4329
+ // basically prevented from updating any form element in the nested schema
4330
+ ui.item.sortable.cancel();
4331
+ }
4332
+ else if ($scope.topLevelFormName) {
4093
4333
  $scope[$scope.topLevelFormName].$setDirty();
4094
4334
  }
4095
- }
4335
+ },
4336
+ // don't do this (see comment above)
4337
+ //cancel: "ol[disabled]>li"
4096
4338
  };
4097
4339
  $scope.setUpCustomLookupOptions = function (schemaElement, ids, options, baseScope) {
4098
4340
  for (var _i = 0, _a = [$scope, baseScope]; _i < _a.length; _i++) {
@@ -4412,8 +4654,17 @@ var fng;
4412
4654
  function clearContextMenu() {
4413
4655
  $scope.items = [];
4414
4656
  $scope.contextMenu = undefined;
4657
+ $scope.contextMenuId = undefined;
4658
+ $scope.contextMenuHidden = undefined;
4659
+ $scope.contextMenuDisabled = undefined;
4415
4660
  }
4416
4661
  $rootScope.navScope = $scope; // Lets plugins access menus
4662
+ $rootScope.isSecurelyHidden = function (elemId) {
4663
+ return fng.formsAngular.elemSecurityFuncName && $rootScope[fng.formsAngular.elemSecurityFuncName](elemId, "hidden");
4664
+ };
4665
+ $rootScope.isSecurelyDisabled = function (elemId) {
4666
+ return fng.formsAngular.elemSecurityFuncName && $rootScope[fng.formsAngular.elemSecurityFuncName](elemId, "disabled");
4667
+ };
4417
4668
  clearContextMenu();
4418
4669
  $scope.toggleCollapsed = function () {
4419
4670
  $scope.collapsed = !$scope.collapsed;
@@ -4489,11 +4740,21 @@ var fng;
4489
4740
  }
4490
4741
  return result;
4491
4742
  };
4743
+ $scope.secureContextMenu = function () {
4744
+ $scope.contextMenuHidden = $rootScope.isSecurelyHidden($scope.contextMenuId);
4745
+ $scope.contextMenuDisabled = $rootScope.isSecurelyDisabled($scope.contextMenuId);
4746
+ };
4747
+ function initialiseContextMenu(menuCaption) {
4748
+ $scope.contextMenu = menuCaption;
4749
+ var menuId = "".concat(_.camelCase(menuCaption), "ContextMenu");
4750
+ $scope.contextMenuId = menuId;
4751
+ $scope.secureContextMenu();
4752
+ }
4492
4753
  $scope.$on('fngControllersLoaded', function (evt, sharedData, modelName) {
4493
- $scope.contextMenu = sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false);
4754
+ initialiseContextMenu(sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false));
4494
4755
  if (sharedData.dropDownDisplayPromise) {
4495
4756
  sharedData.dropDownDisplayPromise.then(function (value) {
4496
- $scope.contextMenu = value;
4757
+ initialiseContextMenu(value);
4497
4758
  });
4498
4759
  }
4499
4760
  });
@@ -4542,8 +4803,9 @@ var fng;
4542
4803
  return item.isHidden ? item.isHidden() : false;
4543
4804
  }
4544
4805
  var dividerHide = false;
4806
+ var item = $scope.items[index];
4545
4807
  // Hide a divider if it appears under another
4546
- if ($scope.items[index].divider) {
4808
+ if (item.divider) {
4547
4809
  if (index === 0) {
4548
4810
  dividerHide = true;
4549
4811
  }
@@ -4563,7 +4825,7 @@ var fng;
4563
4825
  }
4564
4826
  }
4565
4827
  }
4566
- return dividerHide || explicitlyHidden($scope.items[index]);
4828
+ return dividerHide || explicitlyHidden(item);
4567
4829
  };
4568
4830
  $scope.isDisabled = function (index) {
4569
4831
  return $scope.items[index].isDisabled ? $scope.items[index].isDisabled() : false;