forms-angular 0.12.0-beta.19 → 0.12.0-beta.190

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.
@@ -9,13 +9,13 @@ var fng;
9
9
  restrict: 'AE',
10
10
  replace: true,
11
11
  template: '<li ng-show="items.length > 0" class="mcdd" uib-dropdown>' +
12
- ' <a uib-dropdown-toggle>' +
12
+ ' <a href="#" uib-dropdown-toggle>' +
13
13
  ' {{contextMenu}} <b class="caret"></b>' +
14
14
  ' </a>' +
15
15
  ' <ul class="uib-dropdown-menu dropdown-menu">' +
16
16
  ' <li ng-repeat="choice in items" ng-hide="isHidden($index)" ng-class="dropdownClass($index)">' +
17
- ' <a ng-show="choice.text" class="dropdown-option" ng-href="{{choice.url}}" ng-click="doClick($index, $event)">' +
18
- ' {{choice.text}}' +
17
+ ' <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
19
  ' </a>' +
20
20
  ' </li>' +
21
21
  ' </ul>' +
@@ -31,16 +31,11 @@ var fng;
31
31
  var directives;
32
32
  (function (directives) {
33
33
  /*@ngInject*/
34
- function errorDisplay() {
34
+ errorDisplay.$inject = ["cssFrameworkService"];
35
+ function errorDisplay(cssFrameworkService) {
35
36
  return {
36
37
  restrict: 'E',
37
- template: '<div id="display-error" ng-show="errorMessage" ng-class="css(\'rowFluid\')">' +
38
- ' <div class="alert alert-error col-lg-offset-3 offset3 col-lg-6 col-xs-12 span6 alert-warning alert-dismissable">' +
39
- ' <button type="button" class="close" ng-click="dismissError()">×</button>' +
40
- ' <h4>{{alertTitle}}</h4>' +
41
- ' <div ng-bind-html="errorMessage"></div>' +
42
- ' </div>' +
43
- '</div>'
38
+ templateUrl: 'error-display-' + cssFrameworkService.framework() + '.html'
44
39
  };
45
40
  }
46
41
  directives.errorDisplay = errorDisplay;
@@ -48,6 +43,44 @@ var fng;
48
43
  })(fng || (fng = {}));
49
44
  /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
50
45
  var fng;
46
+ (function (fng) {
47
+ var controllers;
48
+ (function (controllers) {
49
+ /*@ngInject*/
50
+ LinkCtrl.$inject = ["$scope"];
51
+ function LinkCtrl($scope) {
52
+ /**
53
+ * In the event that the link is part of a form that belongs to a (ui-bootstrap) modal,
54
+ * close the modal
55
+ */
56
+ $scope.checkNotModal = function () {
57
+ var elm = $scope.element[0];
58
+ var parentNode;
59
+ var finished = false;
60
+ var fakeEvt = {
61
+ preventDefault: angular.noop,
62
+ stopPropagation: angular.noop,
63
+ target: 1,
64
+ currentTarget: 1
65
+ };
66
+ do {
67
+ parentNode = elm.parentNode;
68
+ if (!parentNode) {
69
+ finished = true;
70
+ }
71
+ else if (typeof parentNode.getAttribute === "function" && parentNode.getAttribute('uib-modal-window')) {
72
+ angular.element(elm).scope().close(fakeEvt);
73
+ finished = true;
74
+ }
75
+ else {
76
+ elm = parentNode;
77
+ }
78
+ } while (!finished);
79
+ };
80
+ }
81
+ controllers.LinkCtrl = LinkCtrl;
82
+ })(controllers = fng.controllers || (fng.controllers = {}));
83
+ })(fng || (fng = {}));
51
84
  (function (fng) {
52
85
  var directives;
53
86
  (function (directives) {
@@ -58,38 +91,124 @@ var fng;
58
91
  restrict: 'E',
59
92
  scope: { dataSrc: '&model' },
60
93
  link: function (scope, element, attrs) {
61
- var ref = JSON.parse(attrs['ref']);
94
+ var ref = attrs['ref'];
95
+ var isLabel = attrs['text'] && (unescape(attrs['text']) !== attrs['text']);
62
96
  var form = attrs['form'];
97
+ var linktab = attrs['linktab'];
63
98
  scope['readonly'] = attrs['readonly'];
99
+ scope['element'] = element;
64
100
  form = form ? form + '/' : '';
65
- if (attrs['text'] && attrs['text'].length > 0) {
66
- scope['text'] = attrs['text'];
67
- }
68
- var index = scope['$parent']['$index'];
69
- scope.$watch('dataSrc()', function (newVal) {
70
- if (newVal) {
71
- if (typeof index !== 'undefined' && angular.isArray(newVal)) {
72
- newVal = newVal[index];
73
- }
74
- scope['link'] = routingService.buildUrl(ref.collection + '/' + form + newVal + '/edit');
75
- if (!scope['text']) {
76
- SubmissionsService.getListAttributes(ref, newVal).then(function (response) {
77
- var data = response.data;
78
- if (data.success === false) {
79
- scope['text'] = data.err;
101
+ linktab = linktab ? '/' + linktab : '';
102
+ if (isLabel) {
103
+ var workScope = scope;
104
+ var workString = '';
105
+ while (typeof workScope['baseSchema'] !== "function" && workScope.$parent) {
106
+ if (typeof workScope['$index'] !== "undefined") {
107
+ throw new Error('No support for $index at this level - ' + workString);
108
+ }
109
+ workScope = workScope.$parent;
110
+ workString = workString + '$parent.';
111
+ }
112
+ var attrib = attrs['fld'];
113
+ var watchExpression;
114
+ var splitAttrib = attrib.split('.');
115
+ if (scope.$parent.subDoc && (scope.$parent.subDoc[attrib] || scope.$parent.subDoc[splitAttrib[splitAttrib.length - 1]])) {
116
+ // Support for use in directives in arrays
117
+ if (scope.$parent.subDoc[attrib]) {
118
+ watchExpression = workString + 'subDoc.' + attrib;
119
+ }
120
+ else {
121
+ watchExpression = workString + 'subDoc.' + splitAttrib[splitAttrib.length - 1];
122
+ }
123
+ }
124
+ else {
125
+ if (typeof workScope['$index'] !== "undefined") {
126
+ attrib = splitAttrib.pop();
127
+ attrib = splitAttrib.join('.') + '[' + workScope['$index'] + '].' + attrib;
128
+ }
129
+ else {
130
+ attrib = '.' + attrib;
131
+ }
132
+ watchExpression = workString + 'record' + attrib;
133
+ }
134
+ scope.$watch(watchExpression, function (newVal) {
135
+ if (newVal) {
136
+ if (/^[a-f0-9]{24}/.test(newVal.toString())) {
137
+ newVal = newVal.slice(0, 24);
138
+ }
139
+ else if (newVal.id && /^[a-f0-9]{24}/.test(newVal.id)) {
140
+ newVal = newVal.id.slice(0, 24);
141
+ }
142
+ else if (scope.$parent["f_" + attrs['fld'] + "Options"]) {
143
+ // extract from lookups
144
+ var i = scope.$parent["f_" + attrs['fld'] + "Options"].indexOf(newVal);
145
+ if (i > -1) {
146
+ newVal = scope.$parent["f_" + attrs['fld'] + "_ids"][i];
80
147
  }
81
148
  else {
82
- scope['text'] = data.list;
149
+ newVal = undefined;
83
150
  }
84
- }, function (response) {
85
- scope['text'] = 'Error ' + response.status + ': ' + response.data;
86
- });
151
+ }
152
+ else {
153
+ newVal = undefined;
154
+ }
155
+ if (newVal) {
156
+ scope['link'] = routingService.buildUrl(ref + '/' + form + (newVal.id || newVal) + '/edit' + linktab);
157
+ }
158
+ else {
159
+ scope['link'] = undefined;
160
+ }
87
161
  }
88
- }
89
- }, true);
162
+ else {
163
+ scope['link'] = undefined;
164
+ }
165
+ }, true);
166
+ }
167
+ else {
168
+ if (attrs['text'] && attrs['text'].length > 0) {
169
+ scope['text'] = attrs['text'];
170
+ }
171
+ var index = scope['$parent']['$index'];
172
+ scope.$watch('dataSrc()', function (newVal) {
173
+ if (newVal) {
174
+ if (typeof index !== 'undefined' && angular.isArray(newVal)) {
175
+ newVal = newVal[index];
176
+ }
177
+ scope['link'] = routingService.buildUrl(ref + '/' + form + newVal + '/edit' + linktab);
178
+ if (!scope['text']) {
179
+ SubmissionsService.getListAttributes(ref, newVal).then(function (response) {
180
+ var data = response.data;
181
+ if (data.success === false) {
182
+ scope['text'] = data.err;
183
+ }
184
+ else {
185
+ scope['text'] = data.list;
186
+ }
187
+ }, function (response) {
188
+ scope['text'] = 'Error ' + response.status + ': ' + response.data;
189
+ });
190
+ }
191
+ }
192
+ }, true);
193
+ }
90
194
  },
195
+ controller: "LinkCtrl",
91
196
  template: function (element, attrs) {
92
- return attrs.readonly ? '<span class="fng-link">{{text}}</span>' : '<a href="{{ link }}" class="fng-link">{{text}}</a>';
197
+ function handleAnchor(contents) {
198
+ return "<a ng-click=\"checkNotModal()\" ng-href=\"{{ link || '#' }}\" class=\"fng-link\">".concat(contents, "</a>");
199
+ }
200
+ var retVal;
201
+ if (attrs.readonly) {
202
+ retVal = '<span class="fng-link">{{text}}</span>';
203
+ }
204
+ else if (attrs['text'] && unescape(attrs['text']) !== attrs['text']) {
205
+ retVal = handleAnchor(unescape(attrs['text']));
206
+ // retVal = '<a href="{{ link }}" class="fng-link">{{text}}</a>';
207
+ }
208
+ else {
209
+ retVal = handleAnchor('{{text}}');
210
+ }
211
+ return retVal;
93
212
  }
94
213
  };
95
214
  }
@@ -146,7 +265,7 @@ var fng;
146
265
  // <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
147
266
  // </div>
148
267
  //
149
- // Inline
268
+ // Inline or stacked
150
269
  // <div class="form-group">
151
270
  // <label class="sr-only" for="exampleInputEmail2">Email address</label>
152
271
  // <input type="email" class="form-control" id="exampleInputEmail2" placeholder="Enter email">
@@ -165,7 +284,7 @@ var fng;
165
284
  // <input type="text" placeholder="Type something…">
166
285
  // <span class="help-block">Example block-level help text here.</span>
167
286
  //
168
- // Inline
287
+ // Inline or Stacked
169
288
  // <input type="text" class="input-small" placeholder="Email">
170
289
  var subkeys = [];
171
290
  var tabsSetup = tabsSetupState.N;
@@ -206,7 +325,7 @@ var fng;
206
325
  var lastPart = compoundName.slice(root.length + 1);
207
326
  if (options.index) {
208
327
  modelString += root + '[' + options.index + '].' + lastPart;
209
- idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|\]\.)/g, '-');
328
+ idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|]\.)/g, '-');
210
329
  }
211
330
  else {
212
331
  modelString += root;
@@ -228,18 +347,33 @@ var fng;
228
347
  var allInputsVars = formMarkupHelper.allInputsVars(scope, fieldInfo, options, modelString, idString, nameString);
229
348
  var common = allInputsVars.common;
230
349
  var value;
231
- var requiredStr = (isRequired || fieldInfo.required) ? ' required' : '';
232
350
  isRequired = isRequired || fieldInfo.required;
233
- var requiredStr = isRequired ? ' required' : '';
351
+ var requiredStr = isRequired ? ' required ' : '';
234
352
  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
+ }
235
368
  switch (fieldInfo.type) {
236
369
  case 'select':
237
370
  if (fieldInfo.select2) {
238
371
  value = '<input placeholder="fng-select2 has been removed" readonly>';
239
372
  }
240
373
  else {
241
- common += (fieldInfo.readonly ? 'disabled ' : '');
374
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
242
375
  common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
376
+ common += " aria-label=\"".concat(fieldInfo.label && fieldInfo.label !== "" ? fieldInfo.label : fieldInfo.name, "\" ");
243
377
  value = '<select ' + common + 'class="' + allInputsVars.formControl.trim() + allInputsVars.compactClass + allInputsVars.sizeClassBS2 + '" ' + requiredStr + '>';
244
378
  if (!isRequired) {
245
379
  value += '<option></option>';
@@ -268,25 +402,31 @@ var fng;
268
402
  }
269
403
  break;
270
404
  case 'link':
271
- if (typeof fieldInfo.ref === 'string') {
272
- fieldInfo.ref = { type: 'lookup', collection: fieldInfo.ref };
273
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(fieldInfo.ref));
274
- }
275
- value = '<fng-link model="' + modelString + '" ref="' + JSON.stringify(fieldInfo.ref).replace(/"/g, '&quot;') + '"';
405
+ value = '<fng-link model="' + modelString + '" ref="' + fieldInfo.ref + '"';
276
406
  if (fieldInfo.form) {
277
407
  value += ' form="' + fieldInfo.form + '"';
278
408
  }
279
- if (fieldInfo.linkText) {
280
- value += ' text="' + fieldInfo.linkText + '"';
409
+ if (fieldInfo.linktab) {
410
+ value += ' linktab="' + fieldInfo.linktab + '"';
411
+ }
412
+ if (fieldInfo.linktext) {
413
+ value += ' text="' + fieldInfo.linktext + '"';
281
414
  }
282
415
  if (fieldInfo.readonly) {
283
- value += ' readonly="true"';
416
+ if (typeof fieldInfo.readonly === "boolean") {
417
+ value += " readonly=\"true\"";
418
+ }
419
+ else {
420
+ value += " ng-readonly=\"".concat(fieldInfo.readonly, "\"");
421
+ }
284
422
  }
285
423
  value += '></fng-link>';
286
424
  break;
287
425
  case 'radio':
288
426
  value = '';
289
- common += requiredStr + (fieldInfo.readonly ? ' disabled ' : ' ');
427
+ common += requiredStr;
428
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
429
+ common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
290
430
  var separateLines = options.formstyle === 'vertical' || (options.formstyle !== 'inline' && !fieldInfo.inlineRadio);
291
431
  if (angular.isArray(fieldInfo.options)) {
292
432
  if (options.subschema) {
@@ -296,8 +436,7 @@ var fng;
296
436
  var thisCommon_1;
297
437
  angular.forEach(fieldInfo.options, function (optValue, idx) {
298
438
  thisCommon_1 = common.replace('id="', 'id="' + idx + '-');
299
- value += '<input ' + thisCommon_1 + 'type="radio"';
300
- value += ' value="' + optValue + '">' + optValue;
439
+ value += "<input ".concat(thisCommon_1, " type=\"radio\" aria-label=\"").concat(optValue, "\" value=\"").concat(optValue, "\">").concat(optValue);
301
440
  if (separateLines) {
302
441
  value += '<br />';
303
442
  }
@@ -312,18 +451,16 @@ var fng;
312
451
  }
313
452
  enumInstruction = generateEnumInstructions();
314
453
  value += '<' + tagType + ' ng-repeat="option in ' + enumInstruction.repeat + '">';
315
- value += '<input ' + common.replace('id="', 'id="{{$index}}-') + ' type="radio" value="{{' + enumInstruction.value + '}}"> {{';
316
- value += enumInstruction.label || enumInstruction.value;
317
- value += ' }} </' + tagType + '> ';
454
+ value += "<input ".concat(common.replace('id="', 'id="{{$index}}-'), " type=\"radio\" aria-label=\"").concat(enumInstruction.value, "\" value=\"{{ ").concat(enumInstruction.value, " }}\"> {{ ").concat(enumInstruction.label || enumInstruction.value, " }} </").concat(tagType, "> ");
318
455
  }
319
456
  break;
320
457
  case 'checkbox':
321
- common += requiredStr + (fieldInfo.readonly ? ' disabled ' : ' ');
458
+ common += requiredStr;
459
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
460
+ common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
461
+ value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
322
462
  if (cssFrameworkService.framework() === 'bs3') {
323
- value = '<div class="checkbox"><input ' + common + 'type="checkbox"></div>';
324
- }
325
- else {
326
- value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
463
+ value = '<div class="checkbox">' + value + '</div>';
327
464
  }
328
465
  break;
329
466
  default:
@@ -344,7 +481,7 @@ var fng;
344
481
  allInputsVars.sizeClassBS3 = 'col-xs-12';
345
482
  }
346
483
  }
347
- value = '<textarea ' + common + ' />';
484
+ value = '<textarea ' + common + '></textarea>';
348
485
  }
349
486
  else {
350
487
  value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
@@ -364,6 +501,9 @@ var fng;
364
501
  case 'inline':
365
502
  result = 'form-inline';
366
503
  break;
504
+ case 'stacked':
505
+ result = 'form-stacked';
506
+ break;
367
507
  case 'horizontalCompact':
368
508
  result = 'form-horizontal compact';
369
509
  break;
@@ -391,7 +531,7 @@ var fng;
391
531
  if (tabNo >= 0) {
392
532
  // TODO Figure out tab history updates (check for other tab-history-todos)
393
533
  // result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"'
394
- result.before = '<uib-tab select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"';
534
+ result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"';
395
535
  if (tabNo > 0) {
396
536
  result.before += 'active="tabs[' + tabNo + '].active"';
397
537
  }
@@ -452,6 +592,41 @@ var fng;
452
592
  }
453
593
  return result;
454
594
  };
595
+ var generateInlineHeaders = function (instructionsArray, options, model, evenWhenEmpty) {
596
+ // "column" headers for nested schemas that use formStyle: "inline" will only line up with their respective
597
+ // controls when widths are applied to both the cg_f_xxxx and col_label_xxxx element using css.
598
+ // Likely, the widths will need to be the same, so consider using the following:
599
+ // div[id$="_f_<collection>_<field>"] {
600
+ // width: 100px;
601
+ // }
602
+ // one column can grow to the remaining available width thus:
603
+ // div[id$="_f_<collection>_<field>"] {
604
+ // flex-grow: 1;
605
+ // }
606
+ var hideWhenEmpty = evenWhenEmpty ? "" : "ng-hide=\"!".concat(model, " || ").concat(model, ".length === 0\"");
607
+ var res = "<div class=\"inline-col-headers\" style=\"display:flex\" ".concat(hideWhenEmpty, ">");
608
+ for (var _i = 0, instructionsArray_1 = instructionsArray; _i < instructionsArray_1.length; _i++) {
609
+ var info = instructionsArray_1[_i];
610
+ // need to call this now to ensure the id is set. will probably be (harmlessly) called again later.
611
+ inferMissingProperties(info, options);
612
+ res += '<div ';
613
+ info.showWhen = info.showWhen || info.showwhen; // deal with use within a directive
614
+ if (info.showWhen) {
615
+ if (typeof info.showWhen === 'string') {
616
+ res += 'ng-show="' + info.showWhen + '"';
617
+ }
618
+ else {
619
+ res += 'ng-show="' + formMarkupHelper.generateNgShow(info.showWhen, model) + '"';
620
+ }
621
+ }
622
+ if (info.id && typeof info.id.replace === "function") {
623
+ res += ' id="col_label_' + info.id.replace(/\./g, '-') + '"';
624
+ }
625
+ res += " class=\"inline-col-header\"><label for=\"".concat(info.id, "\" class=\"control-label\">").concat(info.label, "</label></div>");
626
+ }
627
+ res += "</div>";
628
+ return res;
629
+ };
455
630
  var handleField = function (info, options) {
456
631
  var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
457
632
  var template = fieldChrome.template;
@@ -474,7 +649,8 @@ var fng;
474
649
  formstyle: options.formstyle,
475
650
  subkey: schemaDefName + '_subkey',
476
651
  subkeyno: arraySel,
477
- subschemaroot: info.name
652
+ subschemaroot: info.name,
653
+ suppressNestingWarning: info.suppressNestingWarning
478
654
  });
479
655
  template += topAndTail.after;
480
656
  }
@@ -482,7 +658,9 @@ var fng;
482
658
  }
483
659
  else {
484
660
  if (options.subschema) {
485
- console.log('Attempts at supporting deep nesting have been removed - will hopefully be re-introduced at a later date');
661
+ if (!options.suppressNestingWarning) {
662
+ console.log('Attempts at supporting deep nesting have been removed - will hopefully be re-introduced at a later date');
663
+ }
486
664
  }
487
665
  else {
488
666
  var model = (options.model || 'record') + '.' + info.name;
@@ -504,20 +682,24 @@ var fng;
504
682
  }
505
683
  }
506
684
  /* Array body */
507
- template += '<ol class="sub-doc"' + (info.sortable ? " ui-sortable=\"sortableOptions\" ng-model=\"" + model + "\"" : '') + '>';
685
+ if (info.formStyle === "inline" && info.inlineHeaders) {
686
+ template += generateInlineHeaders(info.schema, options, model, info.inlineHeaders === "always");
687
+ }
688
+ template += '<ol class="sub-doc"' + (info.sortable ? " ui-sortable=\"sortableOptions\" ng-model=\"".concat(model, "\"") : '') + '>';
508
689
  template += '<li ng-form class="' + (cssFrameworkService.framework() === 'bs2' ? 'row-fluid ' : '') +
509
- convertFormStyleToClass(info.formStyle) + '" name="form_' + niceName + '{{$index}}" class="sub-doc well" id="' + info.id + 'List_{{$index}}" ' +
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}}" ' +
510
692
  ' ng-repeat="subDoc in ' + model + ' track by $index">';
511
693
  if (cssFrameworkService.framework() === 'bs2') {
512
694
  template += '<div class="row-fluid sub-doc">';
513
695
  }
514
- if (!info.noRemove || info.customSubDoc) {
696
+ if (info.noRemove !== true || info.customSubDoc) {
515
697
  template += ' <div class="sub-doc-btns">';
516
698
  if (typeof info.customSubDoc == 'string') {
517
699
  template += info.customSubDoc;
518
700
  }
519
701
  if (info.noRemove !== true) {
520
- template += "<button " + (info.noRemove ? 'ng-hide="' + info.noRemove + '"' : '') + " name=\"remove_" + info.id + "_btn\" ng-click=\"remove('" + info.name + "', $index, $event)\"";
702
+ template += "<button ".concat(info.noRemove ? 'ng-hide="' + info.noRemove + '"' : '', " name=\"remove_").concat(info.id, "_btn\" ng-click=\"remove('").concat(info.name, "', $index, $event)\"");
521
703
  if (info.remove) {
522
704
  template += ' class="remove-btn btn btn-mini btn-default btn-xs form-btn"><i class="' + formMarkupHelper.glyphClass() + '-minus"></i> Remove';
523
705
  }
@@ -534,31 +716,64 @@ var fng;
534
716
  }
535
717
  template += '</div> ';
536
718
  }
719
+ var parts = void 0;
720
+ if (info.subDocContainerType) {
721
+ var containerType = scope[info.subDocContainerType] || info.subDocContainerType;
722
+ var containerProps = Object.assign({ containerType: containerType }, info.subDocContainerProps);
723
+ parts = containerInstructions(containerProps);
724
+ }
725
+ if (parts === null || parts === void 0 ? void 0 : parts.before) {
726
+ template += parts.before;
727
+ }
537
728
  template += processInstructions(info.schema, false, {
538
729
  subschema: 'true',
539
730
  formstyle: info.formStyle,
540
731
  model: options.model,
541
- subschemaroot: info.name
732
+ subschemaroot: info.name,
733
+ suppressNestingWarning: info.suppressNestingWarning
542
734
  });
735
+ if (parts === null || parts === void 0 ? void 0 : parts.after) {
736
+ template += parts.after;
737
+ }
543
738
  if (cssFrameworkService.framework() === 'bs2') {
544
739
  template += ' </div>';
545
740
  }
546
741
  template += '</li>';
547
742
  template += '</ol>';
548
743
  /* Array footer */
549
- if (info.noAdd !== true || typeof info.customFooter == 'string') {
744
+ if (info.noAdd !== true || typeof info.customFooter == 'string' || info.noneIndicator) {
550
745
  var footer = '';
551
746
  if (typeof info.customFooter == 'string') {
552
747
  footer = info.customFooter;
553
748
  }
554
- if (info.noAdd !== true) {
555
- footer += "<button " + (info.noAdd ? 'ng-hide="' + info.noAdd + '"' : '') + " id=\"add_" + info.id + "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('" + info.name + "',$event)\">\n <i class=\"' + formMarkupHelper.glyphClass() + '-plus\"></i> \n Add\n </button>";
556
- }
557
- if (cssFrameworkService.framework() === 'bs3') {
558
- template += '<div class="row schema-foot"><div class="col-sm-offset-3">' + footer + '</div></div>';
749
+ var hideCond = '';
750
+ var indicatorShowCond = "".concat(model, ".length == 0");
751
+ if (info.noAdd === true) {
752
+ indicatorShowCond = "ng-show=\"".concat(indicatorShowCond, "\"");
559
753
  }
560
754
  else {
561
- template += '<div class = "schema-foot ">' + footer + '</div>';
755
+ hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, "\"") : '';
756
+ indicatorShowCond = info.noAdd ? "ng-show=\"".concat(info.noAdd, " && ").concat(indicatorShowCond, "\"") : '';
757
+ 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>");
758
+ }
759
+ if (info.noneIndicator) {
760
+ footer += "<span ".concat(indicatorShowCond, " class=\"none_indicator\" id=\"no_").concat(info.id, "_indicator\">None</span>");
761
+ // hideCond for the schema-foot is if there's no add button and no indicator
762
+ hideCond = "".concat(model, ".length > 0");
763
+ if (info.noAdd === true) {
764
+ hideCond = "ng-hide=\"".concat(hideCond, "\"");
765
+ }
766
+ else {
767
+ hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, " && ").concat(hideCond, "\"") : '';
768
+ }
769
+ }
770
+ if (footer !== '') {
771
+ if (cssFrameworkService.framework() === 'bs3') {
772
+ template += "<div ".concat(hideCond, " class=\"row schema-foot\"><div class=\"col-sm-offset-3\">").concat(footer, "</div></div>");
773
+ }
774
+ else {
775
+ template += "<div ".concat(hideCond, " class = \"schema-foot \">").concat(footer, "</div>");
776
+ }
562
777
  }
563
778
  }
564
779
  }
@@ -570,8 +785,8 @@ var fng;
570
785
  var controlDivClasses = formMarkupHelper.controlDivClasses(options);
571
786
  if (info.array) {
572
787
  controlDivClasses.push('fng-array');
573
- if (options.formstyle === 'inline') {
574
- throw new Error('Cannot use arrays in an inline form');
788
+ if (options.formstyle === 'inline' || options.formstyle === 'stacked') {
789
+ throw new Error('Cannot use arrays in an inline or stacked form');
575
790
  }
576
791
  template += formMarkupHelper.label(scope, info, info.type !== 'link', options);
577
792
  template += formMarkupHelper.handleArrayInputAndControlDiv(generateInput(info, info.type === 'link' ? null : 'arrayItem.x', true, info.id + '_{{$index}}', options), controlDivClasses, info, options);
@@ -579,25 +794,27 @@ var fng;
579
794
  else {
580
795
  // Single fields here
581
796
  template += formMarkupHelper.label(scope, info, null, options);
582
- if (options.required) {
583
- console.log("********* Options required - found it ********");
584
- }
585
797
  template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, info.id, options), controlDivClasses);
586
798
  }
587
799
  }
588
800
  template += fieldChrome.closeTag;
589
801
  return template;
590
802
  };
591
- var inferMissingProperties = function (info) {
803
+ var inferMissingProperties = function (info, options) {
592
804
  // infer missing values
593
805
  info.type = info.type || 'text';
594
806
  if (info.id) {
595
- if (typeof info.id === 'number' || (info.id[0] >= 0 && info.id <= '9')) {
807
+ if (typeof info.id === 'number' || info.id.match(/^[0-9]/)) {
596
808
  info.id = '_' + info.id;
597
809
  }
598
810
  }
599
811
  else {
600
- info.id = 'f_' + info.name.replace(/\./g, '_');
812
+ if (options && options.noid) {
813
+ info.id = null;
814
+ }
815
+ else {
816
+ info.id = 'f_' + info.name.replace(/\./g, '_');
817
+ }
601
818
  }
602
819
  info.label = (info.label !== undefined) ? (info.label === null ? '' : info.label) : $filter('titleCase')(info.name.split('.').slice(-1)[0]);
603
820
  };
@@ -608,7 +825,11 @@ var fng;
608
825
  if (instructionsArray) {
609
826
  for (var anInstruction = 0; anInstruction < instructionsArray.length; anInstruction++) {
610
827
  var info = instructionsArray[anInstruction];
611
- if (anInstruction === 0 && topLevel && !options.schema.match(/$_schema_/) && typeof info.add !== 'object') {
828
+ if (options.viewform) {
829
+ info = angular.copy(info);
830
+ info.readonly = true;
831
+ }
832
+ if (anInstruction === 0 && topLevel && !options.schema.match(/\$_schema_/) && typeof info.add !== 'object') {
612
833
  info.add = info.add ? ' ' + info.add + ' ' : '';
613
834
  if (info.add.indexOf('ui-date') === -1 && !options.noautofocus && !info.containerType) {
614
835
  info.add = info.add + 'autofocus ';
@@ -617,9 +838,10 @@ var fng;
617
838
  var callHandleField = true;
618
839
  if (info.directive) {
619
840
  var directiveName = info.directive;
620
- var newElement = '<' + directiveName + ' model="' + (options.model || 'record') + '"';
841
+ var newElement = info.customHeader || "";
842
+ newElement += '<' + directiveName + ' model="' + (options.model || 'record') + '"';
621
843
  var thisElement = element[0];
622
- inferMissingProperties(info);
844
+ inferMissingProperties(info, options);
623
845
  for (var i = 0; i < thisElement.attributes.length; i++) {
624
846
  var thisAttr = thisElement.attributes[i];
625
847
  switch (thisAttr.nodeName) {
@@ -655,7 +877,9 @@ var fng;
655
877
  break;
656
878
  case 'object':
657
879
  for (var subAdd in info.add) {
658
- newElement += ' ' + subAdd + '="' + info.add[subAdd].toString().replace(/"/g, '&quot;') + '"';
880
+ if (info.add.hasOwnProperty(subAdd)) {
881
+ newElement += ' ' + subAdd + '="' + info.add[subAdd].toString().replace(/"/g, '&quot;') + '"';
882
+ }
659
883
  }
660
884
  break;
661
885
  default:
@@ -664,13 +888,21 @@ var fng;
664
888
  break;
665
889
  case directiveCamel:
666
890
  for (var subProp in info[prop]) {
667
- newElement += info.directive + '-' + subProp + '="' + info[prop][subProp] + '"';
891
+ if (info[prop].hasOwnProperty(subProp)) {
892
+ newElement += " ".concat(info.directive, "-").concat(subProp, "=\"");
893
+ if (typeof info[prop][subProp] === 'string') {
894
+ newElement += "".concat(info[prop][subProp].replace(/"/g, '&quot;'), "\"");
895
+ }
896
+ else {
897
+ newElement += "".concat(JSON.stringify(info[prop][subProp]).replace(/"/g, '&quot;'), "\"");
898
+ }
899
+ }
668
900
  }
669
901
  break;
670
902
  default:
671
903
  if (info[prop]) {
672
904
  if (typeof info[prop] === 'string') {
673
- newElement += ' fng-fld-' + prop + '="' + info[prop].toString().replace(/"/g, '&quot;') + '"';
905
+ newElement += ' fng-fld-' + prop + '="' + info[prop].replace(/"/g, '&quot;') + '"';
674
906
  }
675
907
  else {
676
908
  newElement += ' fng-fld-' + prop + '="' + JSON.stringify(info[prop]).replace(/"/g, '&quot;') + '"';
@@ -686,6 +918,7 @@ var fng;
686
918
  }
687
919
  }
688
920
  newElement += 'ng-model="' + info.name + '"></' + directiveName + '>';
921
+ newElement += (info.customFooter || "");
689
922
  result += newElement;
690
923
  callHandleField = false;
691
924
  }
@@ -722,9 +955,7 @@ var fng;
722
955
  else if (options.subkey) {
723
956
  // Don't display fields that form part of the subkey, as they should not be edited (because in these circumstances they form some kind of key)
724
957
  var objectToSearch = angular.isArray(scope[options.subkey]) ? scope[options.subkey][0].keyList : scope[options.subkey].keyList;
725
- if (_.find(objectToSearch, function (value, key) {
726
- return scope[options.subkey].path + '.' + key === info.name;
727
- })) {
958
+ if (_.find(objectToSearch, function (value, key) { return scope[options.subkey].path + '.' + key === info.name; })) {
728
959
  callHandleField = false;
729
960
  }
730
961
  }
@@ -732,7 +963,7 @@ var fng;
732
963
  // if (groupId) {
733
964
  // scope['showHide' + groupId] = true;
734
965
  // }
735
- inferMissingProperties(info);
966
+ inferMissingProperties(info, options);
736
967
  result += handleField(info, options);
737
968
  }
738
969
  }
@@ -746,8 +977,9 @@ var fng;
746
977
  var unwatch = scope.$watch(attrs.schema, function (newValue) {
747
978
  if (newValue) {
748
979
  var newArrayValue = angular.isArray(newValue) ? newValue : [newValue]; // otherwise some old tests stop working for no real reason
749
- if (newArrayValue.length > 0) {
980
+ if (newArrayValue.length > 0 && typeof unwatch === "function") {
750
981
  unwatch();
982
+ unwatch = null;
751
983
  var elementHtml = '';
752
984
  var recordAttribute = attrs.model || 'record'; // By default data comes from scope.record
753
985
  var theRecord = scope[recordAttribute];
@@ -761,12 +993,13 @@ var fng;
761
993
  var customAttrs = '';
762
994
  for (var thisAttr in attrs) {
763
995
  if (attrs.hasOwnProperty(thisAttr)) {
764
- if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model'].indexOf(thisAttr) === -1) {
996
+ if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model', 'viewform'].indexOf(thisAttr) === -1) {
765
997
  customAttrs += ' ' + attrs.$attr[thisAttr] + '="' + attrs[thisAttr] + '"';
766
998
  }
767
999
  }
768
1000
  }
769
- elementHtml = '<form name="' + scope.topLevelFormName + '" class="' + convertFormStyleToClass(attrs.formstyle) + ' novalidate"' + customAttrs + '>';
1001
+ var tag = attrs.forceform ? 'ng-form' : 'form';
1002
+ elementHtml = "<".concat(tag, " name=\"").concat(scope.topLevelFormName, "\" class=\"").concat(convertFormStyleToClass(attrs.formstyle), "\" novalidate ").concat(customAttrs, ">");
770
1003
  }
771
1004
  if (theRecord === scope.topLevelFormName) {
772
1005
  throw new Error('Model and Name must be distinct - they are both ' + theRecord);
@@ -782,10 +1015,12 @@ var fng;
782
1015
  // If we have modelControllers we need to let them know when we have form + data
783
1016
  var sharedData = scope[attrs.shared || 'sharedData'];
784
1017
  var modelControllers_1 = sharedData ? sharedData.modelControllers : [];
785
- if (subkeys.length > 0 || modelControllers_1.length > 0) {
1018
+ if ((subkeys.length > 0 || modelControllers_1.length > 0) && !scope.phaseWatcher) {
786
1019
  var unwatch2 = scope.$watch('phase', function (newValue) {
787
- if (newValue === 'ready') {
1020
+ scope.phaseWatcher = true;
1021
+ if (newValue === 'ready' && typeof unwatch2 === "function") {
788
1022
  unwatch2();
1023
+ unwatch2 = null;
789
1024
  // Tell the 'model controllers' that the form and data are there
790
1025
  for (var i = 0; i < modelControllers_1.length; i++) {
791
1026
  if (modelControllers_1[i].onAllReady) {
@@ -814,7 +1049,7 @@ var fng;
814
1049
  arrayOffset = scope[arrayToProcess[thisOffset].selectFunc](theRecord, info);
815
1050
  }
816
1051
  else if (arrayToProcess[thisOffset].keyList) {
817
- // We are chosing the array element by matching one or more keys
1052
+ // We are choosing the array element by matching one or more keys
818
1053
  var thisSubkeyList = arrayToProcess[thisOffset].keyList;
819
1054
  for (arrayOffset = 0; arrayOffset < dataVal.length; arrayOffset++) {
820
1055
  matching = true;
@@ -868,7 +1103,7 @@ var fng;
868
1103
  }
869
1104
  });
870
1105
  }
871
- $rootScope.$broadcast('formInputDone');
1106
+ $rootScope.$broadcast('formInputDone', attrs.name);
872
1107
  if (formGenerator.updateDataDependentDisplay && theRecord && Object.keys(theRecord).length > 0) {
873
1108
  // If this is not a test force the data dependent updates to the DOM
874
1109
  formGenerator.updateDataDependentDisplay(theRecord, null, true, scope);
@@ -917,7 +1152,7 @@ var fng;
917
1152
  /*@ngInject*/
918
1153
  SearchCtrl.$inject = ["$scope", "$http", "$location", "routingService"];
919
1154
  function SearchCtrl($scope, $http, $location, routingService) {
920
- var currentRequest = '';
1155
+ var lastSearchSent;
921
1156
  var _isNotMobile;
922
1157
  _isNotMobile = (function () {
923
1158
  var check = false;
@@ -957,7 +1192,7 @@ var fng;
957
1192
  break;
958
1193
  case 13:
959
1194
  if ($scope.focus != null) {
960
- $scope.selectResult($scope.focus);
1195
+ $location.url(makeUrlNoHtml5Hash($scope.results[$scope.focus]));
961
1196
  }
962
1197
  break;
963
1198
  }
@@ -970,14 +1205,6 @@ var fng;
970
1205
  $scope.results[index].focussed = true;
971
1206
  $scope.focus = index;
972
1207
  };
973
- $scope.selectResult = function (resultNo) {
974
- var result = $scope.results[resultNo];
975
- var newURL = routingService.prefix() + '/' + result.resource + '/' + result.id + '/edit';
976
- if (result.resourceTab) {
977
- newURL += '/' + result.resourceTab;
978
- }
979
- $location.url(newURL);
980
- };
981
1208
  $scope.resultClass = function (index) {
982
1209
  var resultClass = 'search-result';
983
1210
  if ($scope.results && $scope.results[index].focussed) {
@@ -991,16 +1218,23 @@ var fng;
991
1218
  $scope.results = [];
992
1219
  $scope.focus = null;
993
1220
  };
1221
+ function makeUrlNoHtml5Hash(result) {
1222
+ return result.url ? routingService.html5hash() + result.url.replace('|id|', result.id) :
1223
+ routingService.buildOperationUrl('edit', result.resource, undefined, result.id, result.resourceTab);
1224
+ }
994
1225
  $scope.$watch('searchTarget', function (newValue) {
995
1226
  if (newValue && newValue.length > 0) {
996
- currentRequest = newValue;
997
- $http.get('/api/search?q=' + newValue).then(function (response) {
1227
+ lastSearchSent = $scope.testTime || new Date().valueOf();
1228
+ $http.get('/api/search?q=' + newValue + '&sentAt=' + lastSearchSent).then(function (response) {
998
1229
  var data = response.data;
999
1230
  // Check that we haven't fired off a subsequent request, in which
1000
1231
  // case we are no longer interested in these results
1001
- if (currentRequest === newValue) {
1232
+ if (!data.timestamps || !data.timestamps.sentAt || Number.parseInt(data.timestamps.sentAt) === lastSearchSent) {
1002
1233
  if ($scope.searchTarget.length > 0) {
1003
1234
  $scope.results = data.results;
1235
+ $scope.results.forEach(function (result) {
1236
+ result.href = routingService.html5hash() + makeUrlNoHtml5Hash(result);
1237
+ });
1004
1238
  $scope.moreCount = data.moreCount;
1005
1239
  if (data.results.length > 0) {
1006
1240
  $scope.errorClass = '';
@@ -1065,6 +1299,21 @@ var fng;
1065
1299
  })(fng || (fng = {}));
1066
1300
  /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
1067
1301
  var fng;
1302
+ (function (fng) {
1303
+ var filters;
1304
+ (function (filters) {
1305
+ /*@ngInject*/
1306
+ function extractTimestampFromMongoID() {
1307
+ return function (id) {
1308
+ var timestamp = id.substring(0, 8);
1309
+ return new Date(parseInt(timestamp, 16) * 1000);
1310
+ };
1311
+ }
1312
+ filters.extractTimestampFromMongoID = extractTimestampFromMongoID;
1313
+ })(filters = fng.filters || (fng.filters = {}));
1314
+ })(fng || (fng = {}));
1315
+ /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
1316
+ var fng;
1068
1317
  (function (fng) {
1069
1318
  var filters;
1070
1319
  (function (filters) {
@@ -1245,6 +1494,7 @@ var fng;
1245
1494
  controllerName += 'Ctrl';
1246
1495
  locals.$scope = sharedData.modelControllers[level] = localScope;
1247
1496
  var parentScope = localScope.$parent;
1497
+ parentScope.items = parentScope.items || [];
1248
1498
  var addMenuOptions = function (array) {
1249
1499
  angular.forEach(array, function (value) {
1250
1500
  if (value.divider) {
@@ -1276,6 +1526,11 @@ var fng;
1276
1526
  locals.$scope.contextMenuPromise.then(function (array) { return addMenuOptions(array); });
1277
1527
  }
1278
1528
  }
1529
+ if (sharedData.modelNameDisplayPromise) {
1530
+ sharedData.modelNameDisplayPromise.then(function (value) {
1531
+ parentScope.modelNameDisplay = value;
1532
+ });
1533
+ }
1279
1534
  }
1280
1535
  catch (error) {
1281
1536
  // Check to see if error is no such controller - don't care
@@ -1305,7 +1560,7 @@ var fng;
1305
1560
  routing: 'ngroute',
1306
1561
  prefix: '' // How do we want to prefix our routes? If not empty string then first character must be slash (which is added if not)
1307
1562
  };
1308
- var postActions = ['edit'];
1563
+ var postActions = ['edit', 'view'];
1309
1564
  var builtInRoutes = [
1310
1565
  {
1311
1566
  route: '/analyse/:model/:reportSchemaName',
@@ -1315,12 +1570,19 @@ var fng;
1315
1570
  { route: '/analyse/:model', state: 'analyse::model', templateUrl: 'base-analysis.html' },
1316
1571
  { route: '/:model/:id/edit', state: 'model::edit', templateUrl: 'base-edit.html' },
1317
1572
  { route: '/:model/:id/edit/:tab', state: 'model::edit::tab', templateUrl: 'base-edit.html' },
1573
+ { route: '/:model/:id/view', state: 'model::edit', templateUrl: 'base-view.html' },
1574
+ { route: '/:model/:id/view/:tab', state: 'model::view::tab', templateUrl: 'base-view.html' },
1318
1575
  { route: '/:model/new', state: 'model::new', templateUrl: 'base-edit.html' },
1319
1576
  { route: '/:model', state: 'model::list', templateUrl: 'base-list.html' },
1577
+ { route: '/:model/viewonly', state: 'model::view', templateUrl: 'base-list-view.html' },
1578
+ // Non default form (subset of fields etc)
1320
1579
  { route: '/:model/:form/:id/edit', state: 'model::form::edit', templateUrl: 'base-edit.html' },
1321
1580
  { route: '/:model/:form/:id/edit/:tab', state: 'model::form::edit::tab', templateUrl: 'base-edit.html' },
1581
+ { route: '/:model/:form/:id/view', state: 'model::form::view', templateUrl: 'base-view.html' },
1582
+ { route: '/:model/:form/:id/view/:tab', state: 'model::form::view::tab', templateUrl: 'base-view.html' },
1322
1583
  { route: '/:model/:form/new', state: 'model::form::new', templateUrl: 'base-edit.html' },
1323
- { route: '/:model/:form', state: 'model::form::list', templateUrl: 'base-list.html' } // list page with links to non default form
1584
+ { route: '/:model/:form', state: 'model::form::list', templateUrl: 'base-list.html' },
1585
+ { route: '/:model/:form/viewonly', state: 'model::form::list::view', templateUrl: 'base-list-view.html' } // list page with edit links to non default form
1324
1586
  ];
1325
1587
  var _routeProvider, _stateProvider;
1326
1588
  var lastRoute = null;
@@ -1374,6 +1636,12 @@ var fng;
1374
1636
  case 'edit':
1375
1637
  urlStr = modelString + formString + '/' + id + '/edit' + tabString;
1376
1638
  break;
1639
+ case 'view':
1640
+ urlStr = modelString + formString + '/' + id + '/view' + tabString;
1641
+ break;
1642
+ case 'read':
1643
+ urlStr = modelString + formString + '/' + id + '/read' + tabString;
1644
+ break;
1377
1645
  case 'new':
1378
1646
  urlStr = modelString + formString + '/new' + tabString;
1379
1647
  break;
@@ -1456,8 +1724,9 @@ var fng;
1456
1724
  lastObject.modelName = locationSplit[1];
1457
1725
  var lastParts_1 = [locationSplit[locationParts - 1], locationSplit[locationParts - 2]];
1458
1726
  var newPos = lastParts_1.indexOf('new');
1727
+ var viewonlyPos = lastParts_1.indexOf('viewonly');
1459
1728
  var actionPos = void 0;
1460
- if (newPos === -1) {
1729
+ if (newPos === -1 && viewonlyPos === -1) {
1461
1730
  actionPos = postActions.reduce(function (previousValue, currentValue) {
1462
1731
  var pos = lastParts_1.indexOf(currentValue);
1463
1732
  return pos > -1 ? pos : previousValue;
@@ -1467,10 +1736,13 @@ var fng;
1467
1736
  lastObject.id = locationSplit[locationParts];
1468
1737
  }
1469
1738
  }
1470
- else {
1739
+ else if (newPos !== -1) {
1471
1740
  lastObject.newRecord = true;
1472
1741
  locationParts -= (1 + newPos);
1473
1742
  }
1743
+ else {
1744
+ locationParts -= (1 + viewonlyPos);
1745
+ }
1474
1746
  if (actionPos === 1 || newPos === 1) {
1475
1747
  lastObject.tab = lastParts_1[0];
1476
1748
  }
@@ -1481,34 +1753,9 @@ var fng;
1481
1753
  }
1482
1754
  return lastObject;
1483
1755
  };
1484
- ///**
1485
- // * DominicBoettger wrote:
1486
- // *
1487
- // * Parser for the states provided by ui.router
1488
- // */
1489
- //'use strict';
1490
- //formsAngular.factory('$stateParse', [function () {
1491
- //
1492
- // var lastObject = {};
1493
- //
1494
- // return function (state) {
1495
- // if (state.current && state.current.name) {
1496
- // lastObject = {newRecord: false};
1497
- // lastObject.modelName = state.params.model;
1498
- // if (state.current.name === 'model::list') {
1499
- // lastObject = {index: true};
1500
- // lastObject.modelName = state.params.model;
1501
- // } else if (state.current.name === 'model::edit') {
1502
- // lastObject.id = state.params.id;
1503
- // } else if (state.current.name === 'model::new') {
1504
- // lastObject.newRecord = true;
1505
- // } else if (state.current.name === 'model::analyse') {
1506
- // lastObject.analyse = true;
1507
- // }
1508
- // }
1509
- // return lastObject;
1510
- // };
1511
- //}]);
1756
+ },
1757
+ html5hash: function () {
1758
+ return config.html5Mode ? '' : '#';
1512
1759
  },
1513
1760
  buildUrl: function (path) {
1514
1761
  var url = config.html5Mode ? '' : '#';
@@ -1525,46 +1772,25 @@ var fng;
1525
1772
  },
1526
1773
  redirectTo: function () {
1527
1774
  return function (operation, scope, location, id, tab) {
1528
- // switch (config.routing) {
1529
- // case 'ngroute' :
1530
- if (location.search()) {
1531
- location.url(location.path());
1775
+ location.search({}); // Lose any search parameters
1776
+ var urlStr;
1777
+ if (operation === 'onDelete') {
1778
+ if (config.onDelete) {
1779
+ if (config.onDelete === 'new') {
1780
+ urlStr = _buildOperationUrl(config.prefix, 'new', scope.modelName, scope.formName, id, tab);
1781
+ }
1782
+ else {
1783
+ urlStr = config.onDelete;
1784
+ }
1785
+ }
1786
+ else {
1787
+ urlStr = _buildOperationUrl(config.prefix, 'list', scope.modelName, scope.formName, id, tab);
1788
+ }
1789
+ }
1790
+ else {
1791
+ urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
1532
1792
  }
1533
- var urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
1534
1793
  location.path(urlStr);
1535
- // break;
1536
- // case 'uirouter' :
1537
- // var formString = scope.formName ? ('/' + scope.formName) : '';
1538
- // var modelString = config.prefix + '/' + scope.modelName;
1539
- // console.log('form schemas not supported with ui-router');
1540
- // switch (operation) {
1541
- // case 'list' :
1542
- // location.path(modelString + formString);
1543
- // break;
1544
- // case 'edit' :
1545
- // location.path(modelString + formString + '/' + id + '/edit');
1546
- // break;
1547
- // case 'new' :
1548
- // location.path(modelString + formString + '/new');
1549
- // break;
1550
- // }
1551
- // switch (operation) {
1552
- // case 'list' :
1553
- // $state.go('model::list', { model: model });
1554
- // break;
1555
- // case 'edit' :
1556
- // location.path('/' + scope.modelName + formString + '/' + id + '/edit');
1557
- // break;
1558
- // case 'new' :
1559
- // location.path('/' + scope.modelName + formString + '/new');
1560
- // break;
1561
- // }
1562
- // break;
1563
- //
1564
- //
1565
- // // edit: $state.go('model::edit', {id: data._id, model: $scope.modelName });
1566
- // // new: $state.go('model::new', {model: $scope.modelName});
1567
- // break;
1568
1794
  };
1569
1795
  }
1570
1796
  };
@@ -1586,7 +1812,7 @@ var fng;
1586
1812
  * All methods should be state-less
1587
1813
  *
1588
1814
  */
1589
- function formGenerator($location, $timeout, $filter, SubmissionsService, routingService, recordHandler) {
1815
+ function formGenerator($location, $timeout, $filter, routingService, recordHandler) {
1590
1816
  function handleSchema(description, source, destForm, destList, prefix, doRecursion, $scope, ctrlState) {
1591
1817
  function handletabInfo(tabName, thisInst) {
1592
1818
  var tabTitle = angular.copy(tabName);
@@ -1612,50 +1838,55 @@ var fng;
1612
1838
  }
1613
1839
  tab.content.push(thisInst);
1614
1840
  }
1841
+ if (typeof $scope.onSchemaFetch === "function") {
1842
+ $scope.onSchemaFetch(description, source);
1843
+ }
1615
1844
  for (var field in source) {
1616
- if (field === '_id') {
1617
- if (destList && source['_id'].options && source['_id'].options.list) {
1618
- handleListInfo(destList, source['_id'].options.list, field);
1619
- }
1620
- }
1621
- else if (source.hasOwnProperty(field)) {
1622
- var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
1623
- var formData = mongooseOptions.form || {};
1624
- if (mongooseType.schema && !formData.hidden) {
1625
- if (doRecursion && destForm) {
1626
- var schemaSchema = [];
1627
- handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
1628
- var sectionInstructions = basicInstructions(field, formData, prefix);
1629
- sectionInstructions.schema = schemaSchema;
1630
- if (formData.tab) {
1631
- handletabInfo(formData.tab, sectionInstructions);
1632
- }
1633
- if (formData.order !== undefined) {
1634
- destForm.splice(formData.order, 0, sectionInstructions);
1635
- }
1636
- else {
1637
- destForm.push(sectionInstructions);
1638
- }
1845
+ if (source.hasOwnProperty(field)) {
1846
+ if (field === '_id') {
1847
+ if (destList && source['_id'].options && source['_id'].options.list) {
1848
+ handleListInfo(destList, source['_id'].options.list, field);
1639
1849
  }
1640
1850
  }
1641
- else {
1642
- if (destForm && !formData.hidden) {
1643
- var formInstructions = basicInstructions(field, formData, prefix);
1644
- if (handleConditionals(formInstructions.showIf, formInstructions.name, $scope) && field !== 'options') {
1645
- var formInst = handleFieldType(formInstructions, mongooseType, mongooseOptions, $scope, ctrlState);
1646
- if (formInst.tab) {
1647
- handletabInfo(formInst.tab, formInst);
1851
+ else if (source.hasOwnProperty(field)) {
1852
+ var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
1853
+ var formData = mongooseOptions.form || {};
1854
+ if (mongooseType.schema && !formData.hidden) {
1855
+ if (doRecursion && destForm) {
1856
+ var schemaSchema = [];
1857
+ handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
1858
+ var sectionInstructions = basicInstructions(field, formData, prefix);
1859
+ sectionInstructions.schema = schemaSchema;
1860
+ if (formData.tab) {
1861
+ handletabInfo(formData.tab, sectionInstructions);
1648
1862
  }
1649
1863
  if (formData.order !== undefined) {
1650
- destForm.splice(formData.order, 0, formInst);
1864
+ destForm.splice(formData.order, 0, sectionInstructions);
1651
1865
  }
1652
1866
  else {
1653
- destForm.push(formInst);
1867
+ destForm.push(sectionInstructions);
1654
1868
  }
1655
1869
  }
1656
1870
  }
1657
- if (destList && mongooseOptions.list) {
1658
- handleListInfo(destList, mongooseOptions.list, field);
1871
+ else {
1872
+ if (destForm && !formData.hidden) {
1873
+ var formInstructions = basicInstructions(field, formData, prefix);
1874
+ if (handleConditionals(formInstructions.showIf, formInstructions.name, $scope) && field !== 'options') {
1875
+ var formInst = handleFieldType(formInstructions, mongooseType, mongooseOptions, $scope, ctrlState);
1876
+ if (formInst.tab) {
1877
+ handletabInfo(formInst.tab, formInst);
1878
+ }
1879
+ if (formData.order !== undefined) {
1880
+ destForm.splice(formData.order, 0, formInst);
1881
+ }
1882
+ else {
1883
+ destForm.push(formInst);
1884
+ }
1885
+ }
1886
+ }
1887
+ if (destList && mongooseOptions.list) {
1888
+ handleListInfo(destList, mongooseOptions.list, field);
1889
+ }
1659
1890
  }
1660
1891
  }
1661
1892
  }
@@ -1679,6 +1910,9 @@ var fng;
1679
1910
  // console.log($scope.tabs[0]['title'])
1680
1911
  // $location.hash($scope.tabs[0]['title']);
1681
1912
  // }
1913
+ if (typeof $scope.onSchemaProcessed === "function") {
1914
+ $scope.onSchemaProcessed(description, description.slice(0, 5) === 'Main ' ? $scope.baseSchema() : destForm);
1915
+ }
1682
1916
  if (destList && destList.length === 0) {
1683
1917
  handleEmptyList(description, destList, destForm, source);
1684
1918
  }
@@ -1687,23 +1921,23 @@ var fng;
1687
1921
  function performLookupSelect() {
1688
1922
  formInstructions.options = recordHandler.suffixCleanId(formInstructions, 'Options');
1689
1923
  formInstructions.ids = recordHandler.suffixCleanId(formInstructions, '_ids');
1690
- if (!formInstructions.hidden && mongooseOptions.ref) {
1691
- if (typeof mongooseOptions.ref === 'string') {
1692
- mongooseOptions.ref = { type: 'lookup', collection: mongooseOptions.ref };
1693
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(mongooseOptions.ref));
1694
- }
1695
- switch (mongooseOptions.ref.type) {
1696
- case 'lookup':
1697
- recordHandler.setUpLookupOptions(mongooseOptions.ref.collection, formInstructions, $scope, ctrlState, handleSchema);
1698
- break;
1699
- case 'lookupList':
1700
- recordHandler.setUpLookupListOptions(mongooseOptions.ref, formInstructions, $scope, ctrlState);
1701
- break;
1702
- case 'internal':
1703
- recordHandler.handleInternalLookup($scope, formInstructions, mongooseOptions.ref);
1704
- break;
1705
- default:
1706
- throw new Error("Unsupported ref type " + mongooseOptions.ref.type + " found in " + formInstructions.name);
1924
+ if (!formInstructions.hidden) {
1925
+ if (mongooseOptions.ref) {
1926
+ recordHandler.setUpLookupOptions(mongooseOptions.ref, formInstructions, $scope, ctrlState, handleSchema);
1927
+ }
1928
+ else if (mongooseOptions.lookupListRef) {
1929
+ recordHandler.setUpLookupListOptions(mongooseOptions.lookupListRef, formInstructions, $scope, ctrlState);
1930
+ formInstructions.lookupListRef = mongooseOptions.lookupListRef;
1931
+ }
1932
+ else if (mongooseOptions.internalRef) {
1933
+ recordHandler.handleInternalLookup($scope, formInstructions, mongooseOptions.internalRef);
1934
+ formInstructions.internalRef = mongooseOptions.internalRef;
1935
+ }
1936
+ else if (mongooseOptions.customLookupOptions) {
1937
+ // nothing to do - call setUpCustomLookupOptions() when ready to provide id and option arrays
1938
+ }
1939
+ else {
1940
+ throw new Error("No supported select lookup type found in ".concat(formInstructions.name));
1707
1941
  }
1708
1942
  }
1709
1943
  }
@@ -1737,13 +1971,22 @@ var fng;
1737
1971
  }
1738
1972
  else if (mongooseType.instance === 'ObjectID') {
1739
1973
  formInstructions.ref = mongooseOptions.ref;
1740
- if (formInstructions.link && formInstructions.link.linkOnly) {
1741
- formInstructions.type = 'link';
1742
- formInstructions.linkText = formInstructions.link.text;
1974
+ if (formInstructions.link) {
1975
+ if (formInstructions.link.linkOnly) {
1976
+ formInstructions.type = 'link';
1977
+ formInstructions.linktext = formInstructions.link.text;
1978
+ }
1979
+ else if (formInstructions.link.label) {
1980
+ formInstructions.linklabel = true;
1981
+ }
1982
+ else {
1983
+ console.log('Unsupported link setup');
1984
+ }
1743
1985
  formInstructions.form = formInstructions.link.form;
1986
+ formInstructions.linktab = formInstructions.link.linktab;
1744
1987
  delete formInstructions.link;
1745
1988
  }
1746
- else {
1989
+ if (formInstructions.type !== 'link') {
1747
1990
  formInstructions.type = 'select';
1748
1991
  if (formInstructions.select2 || (mongooseOptions.form && mongooseOptions.form.select2)) {
1749
1992
  console.log('support for fng-select2 has been removed in 0.8.3 - please convert to fng-ui-select');
@@ -1755,16 +1998,18 @@ var fng;
1755
1998
  }
1756
1999
  else if (mongooseType.instance === 'Date') {
1757
2000
  if (!formInstructions.type) {
2001
+ formInstructions.intType = 'date';
1758
2002
  if (formInstructions.readonly) {
1759
2003
  formInstructions.type = 'text';
1760
2004
  }
1761
2005
  else if (formInstructions.directive) {
1762
- formInstructions.type = 'text'; // Think they all use date
2006
+ formInstructions.type = 'text';
1763
2007
  }
1764
2008
  else {
1765
2009
  try {
1766
2010
  formInstructions.add = formInstructions.add || '';
1767
- var testDatePickerInstalled = angular.module('ui.date').requires;
2011
+ // Check whether DatePicker is installed
2012
+ angular.module('ui.date').requires;
1768
2013
  formInstructions.type = 'text';
1769
2014
  formInstructions.add += ' ui-date ui-date-format ';
1770
2015
  // formInstructions.add += ' ui-date ui-date-format datepicker-popup-fix ';
@@ -1777,10 +2022,10 @@ var fng;
1777
2022
  }
1778
2023
  }
1779
2024
  else if (mongooseType.instance.toLowerCase() === 'boolean') {
1780
- formInstructions.type = 'checkbox';
2025
+ formInstructions.type = formInstructions.type || 'checkbox';
1781
2026
  }
1782
2027
  else if (mongooseType.instance === 'Number') {
1783
- formInstructions.type = 'number';
2028
+ formInstructions.type = formInstructions.type || 'number';
1784
2029
  if (mongooseOptions.min !== undefined) {
1785
2030
  formInstructions.add = 'min="' + mongooseOptions.min + '" ' + (formInstructions.add || '');
1786
2031
  }
@@ -1800,11 +2045,17 @@ var fng;
1800
2045
  if (mongooseOptions.readonly) {
1801
2046
  formInstructions['readonly'] = true;
1802
2047
  }
2048
+ if (mongooseType.defaultValue !== undefined) {
2049
+ formInstructions.defaultValue = mongooseType.defaultValue;
2050
+ }
2051
+ else if (mongooseType.options && mongooseType.options.default !== undefined) {
2052
+ console.log('No support for default with no value, yet');
2053
+ }
1803
2054
  return formInstructions;
1804
2055
  }
1805
- function getArrayFieldToExtend(fieldName, $scope) {
2056
+ function getArrayFieldToExtend(fieldName, $scope, modelOverride) {
1806
2057
  var fieldParts = fieldName.split('.');
1807
- var arrayField = $scope.record;
2058
+ var arrayField = modelOverride || $scope.record;
1808
2059
  for (var i = 0, l = fieldParts.length; i < l; i++) {
1809
2060
  if (!arrayField[fieldParts[i]]) {
1810
2061
  if (i === l - 1) {
@@ -1921,6 +2172,9 @@ var fng;
1921
2172
  generateEditUrl: function generateEditUrl(obj, $scope) {
1922
2173
  return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/edit');
1923
2174
  },
2175
+ generateViewUrl: function generateViewUrl(obj, $scope) {
2176
+ return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/view');
2177
+ },
1924
2178
  generateNewUrl: function generateNewUrl($scope) {
1925
2179
  return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + 'new');
1926
2180
  },
@@ -2005,25 +2259,44 @@ var fng;
2005
2259
  }
2006
2260
  return forceNextTime;
2007
2261
  },
2008
- add: function add(fieldName, $event, $scope) {
2009
- var arrayField = getArrayFieldToExtend(fieldName, $scope);
2010
- arrayField.push({});
2011
- $scope.setFormDirty($event);
2262
+ add: function add(fieldName, $event, $scope, modelOverride) {
2263
+ var _a;
2264
+ // check that target element is visible. May not be reliable - see https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
2265
+ if ($event.target.offsetParent) {
2266
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2267
+ var schemaElement = $scope.formSchema.find(function (f) { return f.name === fieldName; }); // In case someone is using the formSchema directly
2268
+ var subSchema = schemaElement ? schemaElement.schema : null;
2269
+ var obj = subSchema ? $scope.setDefaults(subSchema, fieldName + '.') : {};
2270
+ if (typeof ((_a = $scope.dataEventFunctions) === null || _a === void 0 ? void 0 : _a.onInitialiseNewSubDoc) === "function") {
2271
+ $scope.dataEventFunctions.onInitialiseNewSubDoc(fieldName, subSchema, obj);
2272
+ }
2273
+ arrayField.push(obj);
2274
+ $scope.setFormDirty($event);
2275
+ }
2012
2276
  },
2013
- unshift: function unshift(fieldName, $event, $scope) {
2014
- var arrayField = getArrayFieldToExtend(fieldName, $scope);
2277
+ unshift: function unshift(fieldName, $event, $scope, modelOverride) {
2278
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2015
2279
  arrayField.unshift({});
2016
2280
  $scope.setFormDirty($event);
2017
2281
  },
2018
- remove: function remove(fieldName, value, $event, $scope) {
2282
+ remove: function remove(fieldName, value, $event, $scope, modelOverride) {
2019
2283
  // Remove an element from an array
2020
- var fieldParts = fieldName.split('.');
2021
- var arrayField = $scope.record;
2022
- for (var i = 0, l = fieldParts.length; i < l; i++) {
2023
- arrayField = arrayField[fieldParts[i]];
2284
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2285
+ var err;
2286
+ if (typeof $scope.dataEventFunctions.onDeleteSubDoc === "function") {
2287
+ var schemaElement = $scope.formSchema.find(function (f) {
2288
+ return f.name === fieldName;
2289
+ });
2290
+ var subSchema = schemaElement ? schemaElement.schema : null;
2291
+ err = $scope.dataEventFunctions.onDeleteSubDoc(fieldName, subSchema, arrayField, value);
2292
+ }
2293
+ if (err) {
2294
+ $scope.showError(err);
2295
+ }
2296
+ else {
2297
+ arrayField.splice(value, 1);
2298
+ $scope.setFormDirty($event);
2024
2299
  }
2025
- arrayField.splice(value, 1);
2026
- $scope.setFormDirty($event);
2027
2300
  },
2028
2301
  hasError: function hasError(formName, name, index, $scope) {
2029
2302
  var result = false;
@@ -2035,7 +2308,7 @@ var fng;
2035
2308
  // Cannot assume that directives will use the same methods
2036
2309
  if (form) {
2037
2310
  var field_1 = form[name];
2038
- if (field_1 && field_1.$invalid) {
2311
+ if (field_1 && field_1.$invalid && !field_1.$$attr.readonly) {
2039
2312
  if (field_1.$dirty) {
2040
2313
  result = true;
2041
2314
  }
@@ -2075,6 +2348,9 @@ var fng;
2075
2348
  $scope.generateEditUrl = function (obj) {
2076
2349
  return formGeneratorInstance.generateEditUrl(obj, $scope);
2077
2350
  };
2351
+ $scope.generateViewUrl = function (obj) {
2352
+ return formGeneratorInstance.generateViewUrl(obj, $scope);
2353
+ };
2078
2354
  $scope.generateNewUrl = function () {
2079
2355
  return formGeneratorInstance.generateNewUrl($scope);
2080
2356
  };
@@ -2082,7 +2358,7 @@ var fng;
2082
2358
  return recordHandlerInstance.scrollTheList($scope);
2083
2359
  };
2084
2360
  $scope.getListData = function (record, fieldName) {
2085
- return recordHandlerInstance.getListData($scope, record, fieldName, $scope.listSchema);
2361
+ return recordHandlerInstance.getListData(record, fieldName, $scope.listSchema, $scope);
2086
2362
  };
2087
2363
  $scope.setPristine = function (clearErrors) {
2088
2364
  if (clearErrors) {
@@ -2104,17 +2380,17 @@ var fng;
2104
2380
  console.log('setFormDirty called without an event (fine in a unit test)');
2105
2381
  }
2106
2382
  };
2107
- $scope.add = function (fieldName, $event) {
2108
- return formGeneratorInstance.add(fieldName, $event, $scope);
2383
+ $scope.add = function (fieldName, $event, modelOverride) {
2384
+ return formGeneratorInstance.add(fieldName, $event, $scope, modelOverride);
2109
2385
  };
2110
2386
  $scope.hasError = function (form, name, index) {
2111
2387
  return formGeneratorInstance.hasError(form, name, index, $scope);
2112
2388
  };
2113
- $scope.unshift = function (fieldName, $event) {
2114
- return formGeneratorInstance.unshift(fieldName, $event, $scope);
2389
+ $scope.unshift = function (fieldName, $event, modelOverride) {
2390
+ return formGeneratorInstance.unshift(fieldName, $event, $scope, modelOverride);
2115
2391
  };
2116
- $scope.remove = function (fieldName, value, $event) {
2117
- return formGeneratorInstance.remove(fieldName, value, $event, $scope);
2392
+ $scope.remove = function (fieldName, value, $event, modelOverride) {
2393
+ return formGeneratorInstance.remove(fieldName, value, $event, $scope, modelOverride);
2118
2394
  };
2119
2395
  $scope.baseSchema = function () {
2120
2396
  return ($scope.tabs.length ? $scope.tabs : $scope.formSchema);
@@ -2129,7 +2405,7 @@ var fng;
2129
2405
  };
2130
2406
  }
2131
2407
  services.formGenerator = formGenerator;
2132
- formGenerator.$inject = ["$location", "$timeout", "$filter", "SubmissionsService", "routingService", "recordHandler"];
2408
+ formGenerator.$inject = ["$location", "$timeout", "$filter", "routingService", "recordHandler"];
2133
2409
  })(services = fng.services || (fng.services = {}));
2134
2410
  })(fng || (fng = {}));
2135
2411
  /// <reference path="../../index.d.ts" />
@@ -2138,8 +2414,8 @@ var fng;
2138
2414
  var services;
2139
2415
  (function (services) {
2140
2416
  /*@ngInject*/
2141
- formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService"];
2142
- function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService) {
2417
+ formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "$filter"];
2418
+ function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, $filter) {
2143
2419
  function generateNgShow(showWhen, model) {
2144
2420
  function evaluateSide(side) {
2145
2421
  var result = side;
@@ -2167,11 +2443,15 @@ var fng;
2167
2443
  }
2168
2444
  return evaluateSide(showWhen.lhs) + conditionSymbols[conditionPos] + evaluateSide(showWhen.rhs);
2169
2445
  }
2170
- var isHorizontalStyle = function isHorizontalStyle(formStyle) {
2171
- return (!formStyle || formStyle === 'undefined' || ['vertical', 'inline'].indexOf(formStyle) === -1);
2446
+ var isHorizontalStyle = function isHorizontalStyle(formStyle, includeStacked) {
2447
+ var exclude = ['vertical', 'inline'];
2448
+ if (!includeStacked) {
2449
+ exclude.push('stacked');
2450
+ }
2451
+ return (!formStyle || formStyle === 'undefined' || !exclude.includes(formStyle));
2172
2452
  };
2173
2453
  function glyphClass() {
2174
- return (cssFrameworkService.framework() === 'bs2') ? 'icon' : 'glyphicon glyphicon';
2454
+ return (cssFrameworkService.framework() === 'bs2' ? 'icon' : 'glyphicon glyphicon');
2175
2455
  }
2176
2456
  return {
2177
2457
  isHorizontalStyle: isHorizontalStyle,
@@ -2189,7 +2469,9 @@ var fng;
2189
2469
  insert += 'ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
2190
2470
  }
2191
2471
  }
2192
- insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
2472
+ if (info.id && typeof info.id.replace === "function") {
2473
+ insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
2474
+ }
2193
2475
  if (cssFrameworkService.framework() === 'bs3') {
2194
2476
  classes += ' form-group';
2195
2477
  if (options.formstyle === 'vertical' && info.size !== 'block-level') {
@@ -2214,7 +2496,7 @@ var fng;
2214
2496
  closeTag += '</div>';
2215
2497
  }
2216
2498
  else {
2217
- if (isHorizontalStyle(options.formstyle)) {
2499
+ if (isHorizontalStyle(options.formstyle, true)) {
2218
2500
  template += '<div' + addAllService.addAll(scope, 'Group', 'control-group', options);
2219
2501
  closeTag = '</div>';
2220
2502
  }
@@ -2228,11 +2510,13 @@ var fng;
2228
2510
  },
2229
2511
  label: function label(scope, fieldInfo, addButtonMarkup, options) {
2230
2512
  var labelHTML = '';
2231
- if ((cssFrameworkService.framework() === 'bs3' || (options.formstyle !== 'inline' && fieldInfo.label !== '')) || addButtonMarkup) {
2513
+ if ((cssFrameworkService.framework() === 'bs3' || (!['inline', 'stacked'].includes(options.formstyle) && fieldInfo.label !== '')) || addButtonMarkup) {
2232
2514
  labelHTML = '<label';
2233
2515
  var classes = 'control-label';
2234
- if (isHorizontalStyle(options.formstyle)) {
2235
- labelHTML += ' for="' + fieldInfo.id + '"';
2516
+ if (isHorizontalStyle(options.formstyle, false)) {
2517
+ if (!fieldInfo.linklabel) {
2518
+ labelHTML += ' for="' + fieldInfo.id + '"';
2519
+ }
2236
2520
  if (typeof fieldInfo.labelDefaultClass !== 'undefined') {
2237
2521
  // Override default label class (can be empty)
2238
2522
  classes += ' ' + fieldInfo.labelDefaultClass;
@@ -2241,7 +2525,7 @@ var fng;
2241
2525
  classes += ' col-sm-3';
2242
2526
  }
2243
2527
  }
2244
- else if (options.formstyle === 'inline') {
2528
+ else if (['inline', 'stacked'].includes(options.formstyle)) {
2245
2529
  labelHTML += ' for="' + fieldInfo.id + '"';
2246
2530
  classes += ' sr-only';
2247
2531
  }
@@ -2250,6 +2534,17 @@ var fng;
2250
2534
  labelHTML += ' <i id="add_' + fieldInfo.id + '" ng-click="add(\'' + fieldInfo.name + '\',$event)" class="' + glyphClass() + '-plus-sign"></i>';
2251
2535
  }
2252
2536
  labelHTML += '</label>';
2537
+ if (fieldInfo.linklabel) {
2538
+ var value = '<fng-link fld="' + fieldInfo.name + '" ref="' + fieldInfo.ref + '" text="' + escape(labelHTML) + '"';
2539
+ if (fieldInfo.form) {
2540
+ value += ' form="' + fieldInfo.form + '"';
2541
+ }
2542
+ if (fieldInfo.linktab) {
2543
+ value += ' linktab="' + fieldInfo.linktab + '"';
2544
+ }
2545
+ value += '></fng-link>';
2546
+ labelHTML = value;
2547
+ }
2253
2548
  }
2254
2549
  return labelHTML;
2255
2550
  },
@@ -2269,14 +2564,17 @@ var fng;
2269
2564
  else {
2270
2565
  sizeClassBS2 = (fieldInfo.size ? ' input-' + fieldInfo.size : '');
2271
2566
  }
2272
- if (options.formstyle === 'inline') {
2567
+ if (['inline', 'stacked'].includes(options.formstyle)) {
2273
2568
  placeHolder = placeHolder || fieldInfo.label;
2274
2569
  }
2275
- common = 'ng-model="' + modelString + '"' + (idString ? ' id="' + idString + '" name="' + idString + '" ' : ' name="' + nameString + '" ');
2570
+ common = 'data-ng-model="' + modelString + '"' + (idString ? ' id="' + idString + '" name="' + idString + '" ' : ' name="' + nameString + '" ');
2276
2571
  common += (placeHolder ? ('placeholder="' + placeHolder + '" ') : '');
2277
2572
  if (fieldInfo.popup) {
2278
2573
  common += 'title="' + fieldInfo.popup + '" ';
2279
2574
  }
2575
+ if (fieldInfo.ariaLabel) {
2576
+ common += 'aria-label="' + fieldInfo.ariaLabel + '" ';
2577
+ }
2280
2578
  common += addAllService.addAll(scope, 'Field', null, options);
2281
2579
  return {
2282
2580
  common: common,
@@ -2287,28 +2585,36 @@ var fng;
2287
2585
  };
2288
2586
  },
2289
2587
  inputChrome: function inputChrome(value, fieldInfo, options, markupVars) {
2290
- if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle) && fieldInfo.type !== 'checkbox') {
2588
+ if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle, true) && fieldInfo.type !== 'checkbox') {
2291
2589
  value = '<div class="bs3-input ' + markupVars.sizeClassBS3 + '">' + value + '</div>';
2292
2590
  }
2293
2591
  // Hack to cope with inline help in directives
2294
2592
  var inlineHelp = (fieldInfo.helpInline || '') + (fieldInfo.helpinline || '');
2295
2593
  if (inlineHelp.length > 0) {
2296
- value += '<span class="' + (cssFrameworkService.framework() === 'bs2' ? 'help-inline' : 'help-block') + '">' + inlineHelp + '</span>';
2297
- }
2298
- // If we have chosen
2299
- value += '<div ng-if="' + (options.name || 'myForm') + '.' + fieldInfo.id + '.$dirty" class="help-block">' +
2300
- ' <div ng-messages="' + (options.name || 'myForm') + '.' + fieldInfo.id + '.$error">' +
2301
- ' <div ng-messages-include="error-messages.html">' +
2302
- ' </div>' +
2303
- ' </div>' +
2304
- '</div>';
2594
+ var helpMarkup = cssFrameworkService.framework() === 'bs2' ? { el: 'span', cl: 'help-inline' } : { el: 'div', cl: 'help-block' };
2595
+ value += "<".concat(helpMarkup.el, " class=\"").concat(helpMarkup.cl, "\">").concat(inlineHelp, "</").concat(helpMarkup.el, ">");
2596
+ }
2597
+ if (!options.noid) {
2598
+ value += "<div ng-if=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$dirty\" class=\"help-block\">") +
2599
+ " <div ng-messages=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$error\">") +
2600
+ ' <div ng-messages-include="error-messages.html">' +
2601
+ ' </div>' +
2602
+ ' </div>' +
2603
+ '</div>';
2604
+ }
2305
2605
  if (fieldInfo.help) {
2306
- value += '<span class="help-block">' + fieldInfo.help + '</span>';
2606
+ value += '<div class="help-block">' + fieldInfo.help + '</div>';
2307
2607
  }
2308
2608
  return value;
2309
2609
  },
2310
2610
  generateSimpleInput: function generateSimpleInput(common, fieldInfo, options) {
2311
- var result = '<input ' + common + 'type="' + fieldInfo.type + '"';
2611
+ var result = '<input ' + common + 'type="' + fieldInfo.type + '" ';
2612
+ if (!fieldInfo.label && !fieldInfo.ariaLabel) {
2613
+ result += "aria-label=\"".concat(fieldInfo.name.replace(/\./g, ' '), "\" ");
2614
+ }
2615
+ else if (options.subschema) {
2616
+ result += "aria-label=\"".concat(fieldInfo.label ? ($filter('titleCase')(options.subschemaroot) + ' ' + fieldInfo.label) : (fieldInfo.popup || fieldInfo.name.replace(/\./g, ' ')), "\" ");
2617
+ }
2312
2618
  if (options.formstyle === 'inline' && cssFrameworkService.framework() === 'bs2' && !fieldInfo.size) {
2313
2619
  result += 'class="input-small"';
2314
2620
  }
@@ -2317,7 +2623,7 @@ var fng;
2317
2623
  },
2318
2624
  controlDivClasses: function controlDivClasses(options) {
2319
2625
  var result = [];
2320
- if (isHorizontalStyle(options.formstyle)) {
2626
+ if (isHorizontalStyle(options.formstyle, false)) {
2321
2627
  result.push(cssFrameworkService.framework() === 'bs2' ? 'controls' : 'col-sm-9');
2322
2628
  }
2323
2629
  return result;
@@ -2351,7 +2657,13 @@ var fng;
2351
2657
  if (fieldInfo.add) {
2352
2658
  result += ' ' + fieldInfo.add + ' ';
2353
2659
  }
2354
- result += requiredStr + (fieldInfo.readonly ? ' readonly' : '') + ' ';
2660
+ result += requiredStr;
2661
+ if (fieldInfo.readonly) {
2662
+ result += " ".concat(typeof fieldInfo.readOnly === 'boolean' ? 'readonly' : 'ng-readonly="' + fieldInfo.readonly + '"', " ");
2663
+ }
2664
+ else {
2665
+ result += ' ';
2666
+ }
2355
2667
  return result;
2356
2668
  }
2357
2669
  };
@@ -2411,15 +2723,17 @@ var fng;
2411
2723
  var options = { formStyle: attr.formstyle };
2412
2724
  var directiveOptions = {};
2413
2725
  var directiveNameLength = directiveName ? directiveName.length : 0;
2726
+ var lcDirectiveName = directiveName === null || directiveName === void 0 ? void 0 : directiveName.toLowerCase();
2414
2727
  for (var prop in attr) {
2415
2728
  if (attr.hasOwnProperty(prop)) {
2416
- if (prop.slice(0, 6) === 'fngFld') {
2417
- info[prop.slice(6).toLowerCase()] = deserialize(attr[prop]);
2729
+ var lcProp = prop.toLowerCase();
2730
+ if (lcProp.slice(0, 6) === 'fngfld') {
2731
+ info[lcProp.slice(6)] = deserialize(attr[prop]);
2418
2732
  }
2419
- else if (prop.slice(0, 6) === 'fngOpt') {
2420
- options[prop.slice(6).toLowerCase()] = deserialize(attr[prop]);
2733
+ else if (lcProp.slice(0, 6) === 'fngopt') {
2734
+ options[lcProp.slice(6)] = deserialize(attr[prop]);
2421
2735
  }
2422
- else if (directiveName && prop.slice(0, directiveNameLength) === directiveName) {
2736
+ else if (directiveName && lcProp.slice(0, directiveNameLength) === lcDirectiveName) {
2423
2737
  directiveOptions[_.kebabCase(prop.slice(directiveNameLength))] = deserialize(attr[prop]);
2424
2738
  }
2425
2739
  }
@@ -2507,26 +2821,32 @@ var fng;
2507
2821
  *
2508
2822
  */
2509
2823
  /*@ngInject*/
2510
- recordHandler.$inject = ["$http", "$location", "$window", "$filter", "$timeout", "routingService", "SubmissionsService", "SchemasService"];
2511
- function recordHandler($http, $location, $window, $filter, $timeout, routingService, SubmissionsService, SchemasService) {
2824
+ recordHandler.$inject = ["$location", "$window", "$filter", "$timeout", "routingService", "cssFrameworkService", "SubmissionsService", "SchemasService"];
2825
+ function recordHandler($location, $window, $filter, $timeout, routingService, cssFrameworkService, SubmissionsService, SchemasService) {
2512
2826
  // TODO: Put this in a service
2513
2827
  var makeMongoId = function (rnd) {
2514
2828
  if (rnd === void 0) { rnd = function (r16) { return Math.floor(r16).toString(16); }; }
2515
- return rnd(Date.now() / 1000) + ' '.repeat(16).replace(/./g, function () { return rnd(Math.random() * 16); });
2829
+ return rnd(Date.now() / 1000) + " ".repeat(16).replace(/./g, function () { return rnd(Math.random() * 16); });
2516
2830
  };
2831
+ function _handleCancel(resp) {
2832
+ if (["cancel", "backdrop click", "escape key press"].indexOf(resp) === -1) {
2833
+ throw resp;
2834
+ }
2835
+ }
2517
2836
  var suffixCleanId = function suffixCleanId(inst, suffix) {
2518
- return (inst.id || 'f_' + inst.name).replace(/\./g, '_') + suffix;
2837
+ return (inst.id || "f_" + inst.name).replace(/\./g, "_") + suffix;
2519
2838
  };
2520
- var walkTree = function (object, fieldname, element) {
2839
+ var walkTree = function (object, fieldname, element, insertIntermediateObjects) {
2521
2840
  // Walk through subdocs to find the required key
2522
2841
  // for instance walkTree(master,'address.street.number',element)
2523
2842
  // called by getData and setData
2843
+ if (insertIntermediateObjects === void 0) { insertIntermediateObjects = false; }
2524
2844
  // element is used when accessing in the context of a input, as the id (like exams-2-grader)
2525
2845
  // gives us the element of an array (one level down only for now). Leaving element blank returns the whole array
2526
- var parts = fieldname.split('.'), higherLevels = parts.length - 1, workingRec = object;
2846
+ var parts = fieldname.split("."), higherLevels = parts.length - 1, workingRec = object;
2527
2847
  for (var i = 0; i < higherLevels; i++) {
2528
2848
  if (!workingRec) {
2529
- throw new Error("walkTree failed: Object = " + object + ", fieldname = " + fieldname + ", i = " + i);
2849
+ throw new Error("walkTree failed: Object = ".concat(object, ", fieldname = ").concat(fieldname, ", i = ").concat(i));
2530
2850
  }
2531
2851
  if (angular.isArray(workingRec)) {
2532
2852
  workingRec = _.map(workingRec, function (obj) {
@@ -2534,18 +2854,21 @@ var fng;
2534
2854
  });
2535
2855
  }
2536
2856
  else {
2857
+ if (insertIntermediateObjects && !workingRec[parts[i]]) {
2858
+ workingRec[parts[i]] = {};
2859
+ }
2537
2860
  workingRec = workingRec[parts[i]];
2538
2861
  }
2539
- if (angular.isArray(workingRec) && typeof element !== 'undefined') {
2540
- if (element.scope && typeof element.scope === 'function') {
2862
+ if (angular.isArray(workingRec) && typeof element !== "undefined") {
2863
+ if (element.scope && typeof element.scope === "function") {
2541
2864
  // If we come across an array we need to find the correct position, if we have an element
2542
2865
  workingRec = workingRec[element.scope().$index];
2543
2866
  }
2544
- else if (typeof element === 'number') {
2867
+ else if (typeof element === "number") {
2545
2868
  workingRec = workingRec[element];
2546
2869
  }
2547
2870
  else {
2548
- throw new Error('Unsupported element type in walkTree ' + fieldname);
2871
+ throw new Error("Unsupported element type in walkTree " + fieldname);
2549
2872
  }
2550
2873
  }
2551
2874
  if (!workingRec) {
@@ -2558,15 +2881,20 @@ var fng;
2558
2881
  };
2559
2882
  };
2560
2883
  var setData = function setData(object, fieldname, element, value) {
2561
- var leafData = walkTree(object, fieldname, element);
2884
+ var leafData = walkTree(object, fieldname, element, !!value);
2562
2885
  if (leafData.lastObject && leafData.key) {
2563
- if (angular.isArray(leafData.lastObject)) {
2564
- for (var i = 0; i < leafData.lastObject.length; i++) {
2565
- leafData.lastObject[i][leafData.key] = value[i];
2886
+ if (value) {
2887
+ if (angular.isArray(leafData.lastObject)) {
2888
+ for (var i = 0; i < leafData.lastObject.length; i++) {
2889
+ leafData.lastObject[i][leafData.key] = value[i];
2890
+ }
2891
+ }
2892
+ else {
2893
+ leafData.lastObject[leafData.key] = value;
2566
2894
  }
2567
2895
  }
2568
2896
  else {
2569
- leafData.lastObject[leafData.key] = value;
2897
+ delete leafData.lastObject[leafData.key];
2570
2898
  }
2571
2899
  }
2572
2900
  };
@@ -2585,11 +2913,17 @@ var fng;
2585
2913
  }
2586
2914
  return retVal;
2587
2915
  };
2588
- var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState) {
2916
+ var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState, ignoreDirty) {
2917
+ if (ignoreDirty === void 0) { ignoreDirty = false; }
2589
2918
  // Update the master and the record with the lookup values, master first
2590
- if (!$scope.topLevelFormName || $scope[$scope.topLevelFormName].$pristine) {
2919
+ if (!$scope.topLevelFormName || ($scope[$scope.topLevelFormName] && (ignoreDirty || $scope[$scope.topLevelFormName].$pristine))) {
2591
2920
  updateObject(schemaElement.name, ctrlState.master, function (value) {
2592
- return convertForeignKeys(schemaElement, value, $scope[suffixCleanId(schemaElement, 'Options')], $scope[suffixCleanId(schemaElement, '_ids')]);
2921
+ if (typeof value == "object" && value.id) {
2922
+ return value;
2923
+ }
2924
+ else {
2925
+ return convertForeignKeys(schemaElement, value, $scope[suffixCleanId(schemaElement, "Options")], $scope[suffixCleanId(schemaElement, "_ids")]);
2926
+ }
2593
2927
  });
2594
2928
  // Then copy the converted keys from master into record
2595
2929
  var newVal = getData(ctrlState.master, schemaElement.name);
@@ -2600,38 +2934,29 @@ var fng;
2600
2934
  };
2601
2935
  // Split a field name into the next level and all following levels
2602
2936
  function splitFieldName(aFieldName) {
2603
- var nesting = aFieldName.split('.'), result = [nesting[0]];
2937
+ var nesting = aFieldName.split("."), result = [nesting[0]];
2604
2938
  if (nesting.length > 1) {
2605
- result.push(nesting.slice(1).join('.'));
2939
+ result.push(nesting.slice(1).join("."));
2606
2940
  }
2607
2941
  return result;
2608
2942
  }
2609
- var getListData = function getListData($scope, record, fieldName, listSchema) {
2943
+ var getListData = function getListData(record, fieldName, listSchema, $scope) {
2610
2944
  if (listSchema === void 0) { listSchema = null; }
2611
- var retVal = record;
2612
- var nests = fieldName.split('.');
2613
- for (var i = 0; i < nests.length; i++) {
2614
- if (retVal !== undefined && retVal !== null && nests && nests[i]) {
2615
- retVal = retVal[nests[i]];
2616
- }
2617
- }
2618
- if (retVal === undefined) {
2619
- retVal = '';
2620
- }
2945
+ var retVal = getData(record, fieldName) || "";
2621
2946
  if (retVal && listSchema) {
2622
2947
  // Convert list fields as per instructions in params (ideally should be the same as what is found in data_form getListFields
2623
- var schemaElm = _.find(listSchema, function (elm) { return (elm['name'] === fieldName); });
2948
+ var schemaElm = _.find(listSchema, function (elm) { return (elm["name"] === fieldName); });
2624
2949
  if (schemaElm) {
2625
- switch (schemaElm['params']) {
2950
+ switch (schemaElm["params"]) {
2626
2951
  case undefined:
2627
2952
  break;
2628
- case 'timestamp':
2953
+ case "timestamp":
2629
2954
  var timestamp = retVal.toString().substring(0, 8);
2630
2955
  var date = new Date(parseInt(timestamp, 16) * 1000);
2631
- retVal = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
2956
+ retVal = date.toLocaleDateString() + " " + date.toLocaleTimeString();
2632
2957
  break;
2633
2958
  default:
2634
- retVal = $scope.dataEventFunctions[schemaElm['params']](record);
2959
+ retVal = $scope.dataEventFunctions[schemaElm["params"]](record);
2635
2960
  }
2636
2961
  }
2637
2962
  }
@@ -2648,7 +2973,7 @@ var fng;
2648
2973
  if (angular.isArray(theValue)) {
2649
2974
  for (var i = theValue.length - 1; i >= 0; i--) {
2650
2975
  var type = typeof theValue[i];
2651
- if (type === 'undefined' || (type === 'object' && Object.keys(theValue[i]).length === 0)) {
2976
+ if (type === "undefined" || (type === "object" && Object.keys(theValue[i]).length === 0)) {
2652
2977
  theValue.splice(i, 1);
2653
2978
  }
2654
2979
  }
@@ -2670,8 +2995,8 @@ var fng;
2670
2995
  }
2671
2996
  // Set up the lookup lists (value and id) on the scope for an internal lookup. Called by convertToAngularModel and $watch
2672
2997
  function setUpInternalLookupLists($scope, options, ids, newVal, valueAttrib) {
2673
- var optionsArray = (typeof options === 'string' ? $scope[options] : options);
2674
- var idsArray = (typeof ids === 'string' ? $scope[ids] : ids);
2998
+ var optionsArray = (typeof options === "string" ? $scope[options] : options);
2999
+ var idsArray = (typeof ids === "string" ? $scope[ids] : ids);
2675
3000
  optionsArray.length = 0;
2676
3001
  idsArray.length = 0;
2677
3002
  if (!!newVal && (newVal.length > 0)) {
@@ -2693,10 +3018,10 @@ var fng;
2693
3018
  result = true;
2694
3019
  }
2695
3020
  else if (!aSchema.directive) {
2696
- if (aSchema.type === 'text') {
3021
+ if (aSchema.type === "text") {
2697
3022
  result = true;
2698
3023
  }
2699
- else if (aSchema.type === 'select' && !aSchema.ids) {
3024
+ else if (aSchema.type === "select" && !aSchema.ids) {
2700
3025
  result = true;
2701
3026
  }
2702
3027
  }
@@ -2706,7 +3031,7 @@ var fng;
2706
3031
  function getConversionObject(scope, entryName, schemaName) {
2707
3032
  var conversions = scope.conversions;
2708
3033
  if (schemaName) {
2709
- conversions = conversions[schemaName] || {};
3034
+ conversions = getData(conversions, schemaName) || {};
2710
3035
  }
2711
3036
  return conversions[entryName];
2712
3037
  }
@@ -2717,37 +3042,58 @@ var fng;
2717
3042
  for (var i = 0; i < schema.length; i++) {
2718
3043
  var schemaEntry = schema[i];
2719
3044
  var fieldName = schemaEntry.name.slice(prefixLength);
3045
+ if (!fieldName.length) {
3046
+ fieldName = schemaEntry.name.split('.').pop();
3047
+ }
2720
3048
  var fieldValue = getData(anObject, fieldName);
3049
+ if (schemaEntry.intType === 'date' && typeof fieldValue === 'string') {
3050
+ setData(anObject, fieldName, null, new Date(fieldValue));
3051
+ }
2721
3052
  if (schemaEntry.schema) {
2722
3053
  if (fieldValue) {
2723
3054
  for (var j = 0; j < fieldValue.length; j++) {
2724
- fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j], prefixLength + 1 + fieldName.length, $scope, fieldName, master, j);
3055
+ fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j], 1 + fieldName.length, $scope, fieldName, master, j);
2725
3056
  }
2726
3057
  }
2727
3058
  }
2728
3059
  else {
2729
- if (schemaEntry.ref && schemaEntry.ref.type === 'internal') {
2730
- setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.ref.property], schemaEntry.ref.value);
3060
+ if (schemaEntry.internalRef) {
3061
+ setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.internalRef.property], schemaEntry.internalRef.value);
2731
3062
  }
2732
3063
  // Convert {array:['item 1']} to {array:[{x:'item 1'}]}
2733
- var thisField = getListData($scope, anObject, fieldName);
2734
- if (schemaEntry.array && simpleArrayNeedsX(schemaEntry) && thisField) {
3064
+ var thisField = getListData(anObject, fieldName, null, $scope);
3065
+ if (schemaEntry.array &&
3066
+ simpleArrayNeedsX(schemaEntry) &&
3067
+ thisField &&
3068
+ !(thisField.length > 0 && thisField[0].x) // Don't keep on coverting
3069
+ ) {
2735
3070
  for (var k = 0; k < thisField.length; k++) {
2736
3071
  thisField[k] = { x: thisField[k] };
2737
3072
  }
2738
3073
  }
2739
3074
  // Convert {lookup:'012abcde'} to {lookup:'List description for 012abcde'}
2740
- var idList = $scope[suffixCleanId(schemaEntry, '_ids')];
3075
+ var idList = $scope[suffixCleanId(schemaEntry, "_ids")];
2741
3076
  var thisConversion = void 0;
2742
3077
  if (fieldValue && idList && idList.length > 0) {
2743
- if (fieldName.indexOf('.') !== -1) {
2744
- throw new Error('Trying to directly assign to a nested field 332');
3078
+ if (fieldName.indexOf(".") !== -1) {
3079
+ throw new Error("Trying to directly assign to a nested field 332");
2745
3080
  } // Not sure that this can happen, but put in a runtime test
2746
- anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, 'Options')], idList);
3081
+ if (
3082
+ /*
3083
+ Check we are starting with an ObjectId (ie not being called because of $watch on conversion, with a
3084
+ converted value, which would cause an exception)
3085
+ */
3086
+ fieldValue.toString().match(/^[a-f0-9]{24}$/) &&
3087
+ /*
3088
+ We are not suppressing conversions
3089
+ */
3090
+ (!schemaEntry.internalRef || !schemaEntry.internalRef.noConvert)) {
3091
+ anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, "Options")], idList);
3092
+ }
2747
3093
  }
2748
3094
  else if (schemaEntry.select2) {
2749
3095
  // Do nothing with these - handled elsewhere (and deprecated)
2750
- console.log('fng-select2 is deprecated - use fng-ui-select instead');
3096
+ console.log("fng-select2 is deprecated - use fng-ui-select instead");
2751
3097
  void (schemaEntry.select2);
2752
3098
  }
2753
3099
  else if (fieldValue && (thisConversion = getConversionObject($scope, fieldName, schemaName)) &&
@@ -2756,7 +3102,7 @@ var fng;
2756
3102
  thisConversion.fngajax(fieldValue, schemaEntry, function (updateEntry, value) {
2757
3103
  // Update the master and (preserving pristine if appropriate) the record
2758
3104
  setData(master, updateEntry.name, offset, value);
2759
- preservePristine(angular.element('#' + updateEntry.id), function () {
3105
+ preservePristine(angular.element("#" + updateEntry.id), function () {
2760
3106
  setData($scope.record, updateEntry.name, offset, value);
2761
3107
  });
2762
3108
  });
@@ -2769,9 +3115,9 @@ var fng;
2769
3115
  // Called when the model is read and when the lookups are read
2770
3116
  // No support for nested schemas here as it is called from convertToAngularModel which does that
2771
3117
  function convertForeignKeys(schemaElement, input, values, ids) {
2772
- if (schemaElement.array) {
3118
+ if (schemaElement.array || angular.isArray(input)) {
2773
3119
  var returnArray = [];
2774
- var needsX = !schemaElement.directive || simpleArrayNeedsX(schemaElement);
3120
+ var needsX = schemaElement.array && (!schemaElement.directive || simpleArrayNeedsX(schemaElement));
2775
3121
  for (var j = 0; j < input.length; j++) {
2776
3122
  var val = input[j];
2777
3123
  if (val && val.x) {
@@ -2815,7 +3161,7 @@ var fng;
2815
3161
  else {
2816
3162
  var index = valuesArray.indexOf(textToConvert);
2817
3163
  if (index === -1) {
2818
- throw new Error('convertListValueToId: Invalid data - value ' + textToConvert + ' not found in ' + valuesArray + ' processing ' + fname);
3164
+ throw new Error("convertListValueToId: Invalid data - value " + textToConvert + " not found in " + valuesArray + " processing " + fname);
2819
3165
  }
2820
3166
  return idsArray[index];
2821
3167
  }
@@ -2823,7 +3169,7 @@ var fng;
2823
3169
  var preservePristine = function preservePristine(element, fn) {
2824
3170
  // stop the form being set to dirty when a fn is called
2825
3171
  // Use when the record (and master) need to be updated by lookup values displayed asynchronously
2826
- var modelController = element.inheritedData('$ngModelController');
3172
+ var modelController = element.inheritedData("$ngModelController");
2827
3173
  var isClean = (modelController && modelController.$pristine);
2828
3174
  if (isClean) {
2829
3175
  // fake it to dirty here and reset after call to fn
@@ -2835,18 +3181,21 @@ var fng;
2835
3181
  }
2836
3182
  };
2837
3183
  var convertIdToListValue = function convertIdToListValue(id, idsArray, valuesArray, fname) {
2838
- if (typeof (id) === 'object') {
3184
+ if (typeof (id) === "object") {
2839
3185
  id = id.id;
2840
3186
  }
2841
3187
  var index = idsArray.indexOf(id);
2842
3188
  if (index === -1) {
2843
- throw new Error('convertIdToListValue: Invalid data - id ' + id + ' not found in ' + idsArray + ' processing ' + fname);
3189
+ index = valuesArray.indexOf(id); // This can get called twice - second time with converted value (not sure how atm) so protect against that...
3190
+ if (index === -1) {
3191
+ throw new Error("convertIdToListValue: Invalid data - id " + id + " not found in " + idsArray + " processing " + fname);
3192
+ }
2844
3193
  }
2845
3194
  return valuesArray[index];
2846
3195
  };
2847
3196
  var processServerData = function processServerData(recordFromServer, $scope, ctrlState) {
2848
3197
  ctrlState.master = convertToAngularModel($scope.formSchema, recordFromServer, 0, $scope);
2849
- $scope.phase = 'ready';
3198
+ $scope.phase = "ready";
2850
3199
  $scope.cancel();
2851
3200
  };
2852
3201
  function convertOldToNew(ref, val, attrib, newVals, oldVals) {
@@ -2865,102 +3214,169 @@ var fng;
2865
3214
  var listOnly = (!$scope.id && !$scope.newRecord);
2866
3215
  // passing null for formSchema parameter prevents all the work being done when we are just after the list data,
2867
3216
  // but should be removed when/if formschemas are cached
2868
- formGeneratorInstance.handleSchema('Main ' + $scope.modelName, schema, listOnly ? null : $scope.formSchema, $scope.listSchema, '', true, $scope, ctrlState);
3217
+ formGeneratorInstance.handleSchema("Main " + $scope.modelName, schema, listOnly ? null : $scope.formSchema, $scope.listSchema, "", true, $scope, ctrlState);
3218
+ function processLookupHandlers(newValue, oldValue) {
3219
+ // If we have any internal lookups then update the references
3220
+ $scope.internalLookups.forEach(function (lkp) {
3221
+ var newVal = newValue[lkp.ref.property];
3222
+ var oldVal = oldValue[lkp.ref.property];
3223
+ setUpInternalLookupLists($scope, lkp.lookupOptions, lkp.lookupIds, newVal, lkp.ref.value);
3224
+ // now change the looked-up values that matched the old to the new
3225
+ if ((newVal && newVal.length > 0) || (oldVal && oldVal.length > 0)) {
3226
+ lkp.handlers.forEach(function (h) {
3227
+ if (h.possibleArray) {
3228
+ var arr = getData($scope.record, h.possibleArray, null);
3229
+ if (arr && arr.length > 0) {
3230
+ arr.forEach(function (a) { return convertOldToNew(lkp.ref, a, h.lastPart, newVal, oldVal); });
3231
+ }
3232
+ }
3233
+ else if (angular.isArray($scope.record[h.lastPart])) {
3234
+ $scope.record[h.lastPart].forEach(function (a) {
3235
+ convertOldToNew(lkp.ref, a, "x", newVal, oldVal);
3236
+ });
3237
+ }
3238
+ else {
3239
+ convertOldToNew(lkp.ref, $scope.record, h.lastPart, newVal, oldVal);
3240
+ }
3241
+ });
3242
+ }
3243
+ });
3244
+ // If we have any list lookups then update the references
3245
+ $scope.listLookups.forEach(function (lkp) {
3246
+ function extractIdVal(obj, idString) {
3247
+ var retVal = obj[idString];
3248
+ if (retVal && retVal.id) {
3249
+ retVal = retVal.id;
3250
+ }
3251
+ return retVal;
3252
+ }
3253
+ function blankListLookup(inst) {
3254
+ setData($scope.record, inst.name);
3255
+ }
3256
+ var idString = lkp.ref.id.slice(1);
3257
+ if (idString.includes(".")) {
3258
+ throw new Error("No support for nested list lookups yet - ".concat(JSON.stringify(lkp.ref)));
3259
+ }
3260
+ var newVal = extractIdVal(newValue, idString);
3261
+ var oldVal = extractIdVal(oldValue, idString);
3262
+ if (newVal !== oldVal) {
3263
+ if (newVal) {
3264
+ if (oldVal) {
3265
+ lkp.handlers.forEach(function (h) {
3266
+ h.oldValue = getData($scope.record, h.formInstructions.name);
3267
+ if (angular.isArray(h.oldValue)) {
3268
+ h.oldId = h.oldValue.map(function (a) {
3269
+ return $scope[h.formInstructions.ids][$scope[h.formInstructions.options].indexOf(a)];
3270
+ });
3271
+ }
3272
+ else {
3273
+ h.oldId = $scope[h.formInstructions.ids][$scope[h.formInstructions.options].indexOf(h.oldValue)];
3274
+ }
3275
+ });
3276
+ }
3277
+ SubmissionsService.readRecord(lkp.ref.collection, newVal).then(function (response) {
3278
+ lkp.handlers.forEach(function (h) {
3279
+ var optionsList = $scope[h.formInstructions.options];
3280
+ optionsList.length = 0;
3281
+ var idList = $scope[h.formInstructions.ids];
3282
+ idList.length = 0;
3283
+ var data = response.data[lkp.ref.property] || [];
3284
+ for (var i = 0; i < data.length; i++) {
3285
+ var option = data[i][lkp.ref.value];
3286
+ var pos = _.sortedIndex(optionsList, option);
3287
+ // handle dupes
3288
+ if (optionsList[pos] === option) {
3289
+ option = option + " (" + data[i]._id + ")";
3290
+ pos = _.sortedIndex(optionsList, option);
3291
+ }
3292
+ optionsList.splice(pos, 0, option);
3293
+ idList.splice(pos, 0, data[i]._id);
3294
+ }
3295
+ if (Object.keys(oldValue).length === 0) {
3296
+ // Not sure how safe this is, but the record is fresh so I think it's OK...
3297
+ updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState, true);
3298
+ }
3299
+ else if (h.oldId) {
3300
+ // Here we are reacting to a change in the lookup pointer in the record.
3301
+ // If the old id exists in the new idList we can keep it, otherwise we need to blank it.
3302
+ // We need to remember that we can have an array of ids
3303
+ if (angular.isArray(h.oldId)) {
3304
+ h.oldId.forEach(function (id, idx) {
3305
+ var pos = idList.indexOf(id);
3306
+ setData($scope.record, h.formInstructions.name, idx, pos === -1 ? undefined : optionsList[pos]);
3307
+ });
3308
+ }
3309
+ else {
3310
+ var pos_1 = idList.indexOf(h.oldId);
3311
+ if (pos_1 !== -1) {
3312
+ setData($scope.record, h.formInstructions.name, undefined, optionsList[pos_1]);
3313
+ }
3314
+ else {
3315
+ blankListLookup(h.formInstructions);
3316
+ }
3317
+ }
3318
+ }
3319
+ else {
3320
+ blankListLookup(h.formInstructions);
3321
+ }
3322
+ });
3323
+ });
3324
+ }
3325
+ else {
3326
+ lkp.handlers.forEach(function (h) {
3327
+ $scope[h.formInstructions.options].length = 0;
3328
+ $scope[h.formInstructions.ids].length = 0;
3329
+ blankListLookup(h.formInstructions);
3330
+ });
3331
+ }
3332
+ }
3333
+ });
3334
+ }
3335
+ function notifyReady() {
3336
+ $scope.phase = "ready";
3337
+ $scope.cancel();
3338
+ processLookupHandlers($scope.record, {});
3339
+ }
2869
3340
  if (listOnly) {
2870
3341
  ctrlState.allowLocationChange = true;
2871
3342
  }
2872
3343
  else {
2873
3344
  var force = true;
2874
3345
  if (!$scope.newRecord) {
2875
- $scope.dropConversionWatcher = $scope.$watchCollection('conversions', function (newValue, oldValue) {
3346
+ $scope.dropConversionWatcher = $scope.$watchCollection("conversions", function (newValue, oldValue) {
2876
3347
  if (newValue !== oldValue && $scope.originalData) {
2877
3348
  processServerData($scope.originalData, $scope, ctrlState);
2878
3349
  }
2879
3350
  });
2880
3351
  }
2881
- $scope.$watch('record', function (newValue, oldValue) {
3352
+ $scope.$watch("record", function (newValue, oldValue) {
2882
3353
  if (newValue !== oldValue) {
2883
3354
  if (Object.keys(oldValue).length > 0 && $scope.dropConversionWatcher) {
2884
3355
  $scope.dropConversionWatcher(); // Don't want to convert changed data
2885
3356
  $scope.dropConversionWatcher = null;
2886
3357
  }
2887
3358
  force = formGeneratorInstance.updateDataDependentDisplay(newValue, oldValue, force, $scope);
2888
- // If we have any internal lookups then update the references
2889
- $scope.internalLookups.forEach(function (lkp) {
2890
- var newVal = newValue[lkp.ref.property];
2891
- var oldVal = oldValue[lkp.ref.property];
2892
- setUpInternalLookupLists($scope, lkp.lookupOptions, lkp.lookupIds, newVal, lkp.ref.value);
2893
- // now change the looked-up values that matched the old to the new
2894
- if ((newVal && newVal.length > 0) || (oldVal && oldVal.length > 0)) {
2895
- lkp.handlers.forEach(function (h) {
2896
- if (h.possibleArray) {
2897
- var arr = getData($scope.record, h.possibleArray, null);
2898
- if (arr && arr.length > 0) {
2899
- arr.forEach(function (a) { return convertOldToNew(lkp.ref, a, h.lastPart, newVal, oldVal); });
2900
- }
2901
- }
2902
- else if (angular.isArray($scope.record[h.lastPart])) {
2903
- $scope.record[h.lastPart].forEach(function (a) {
2904
- convertOldToNew(lkp.ref, a, 'x', newVal, oldVal);
2905
- });
2906
- }
2907
- else {
2908
- convertOldToNew(lkp.ref, $scope.record, h.lastPart, newVal, oldVal);
2909
- }
2910
- });
3359
+ processLookupHandlers(newValue, oldValue);
3360
+ if (fng.formsAngular.title) {
3361
+ var title = fng.formsAngular.title.prefix || '';
3362
+ if ($scope['editFormHeader']) {
3363
+ title += $scope['editFormHeader']();
2911
3364
  }
2912
- });
2913
- // If we have any list lookups then update the references
2914
- $scope.listLookups.forEach(function (lkp) {
2915
- function extractIdVal(obj, idString) {
2916
- var retVal = obj[idString];
2917
- if (retVal && retVal.id) {
2918
- retVal = retVal.id;
2919
- }
2920
- return retVal;
2921
- }
2922
- var idString = lkp.ref.id.slice(1);
2923
- if (idString.includes('.')) {
2924
- throw new Error("No support for nested list lookups yet - " + JSON.stringify(lkp.ref));
2925
- }
2926
- var newVal = extractIdVal(newValue, idString);
2927
- var oldVal = extractIdVal(oldValue, idString);
2928
- if (newVal !== oldVal) {
2929
- if (newVal) {
2930
- SubmissionsService.readRecord(lkp.ref.collection, newVal).then(function (response) {
2931
- lkp.handlers.forEach(function (h) {
2932
- var optionsList = $scope[h.formInstructions.options];
2933
- var idList = $scope[h.formInstructions.ids];
2934
- var data = response.data[lkp.ref.property] || [];
2935
- for (var i = 0; i < data.length; i++) {
2936
- var option = data[i][lkp.ref.value];
2937
- var pos = _.sortedIndex(optionsList, option);
2938
- // handle dupes
2939
- if (optionsList[pos] === option) {
2940
- option = option + ' (' + data[i]._id + ')';
2941
- pos = _.sortedIndex(optionsList, option);
2942
- }
2943
- optionsList.splice(pos, 0, option);
2944
- idList.splice(pos, 0, data[i]._id);
2945
- }
2946
- updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState);
2947
- });
2948
- });
2949
- }
2950
- else {
2951
- lkp.handlers.forEach(function (h) {
2952
- $scope[h.formInstructions.options].length = 0;
2953
- $scope[h.formInstructions.ids].length = 0;
2954
- updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState);
2955
- });
3365
+ else {
3366
+ for (var listElm in $scope.listSchema) {
3367
+ if ($scope.listSchema.hasOwnProperty(listElm)) {
3368
+ title += $scope.getListData($scope.record, $scope.listSchema[listElm].name) + ' ';
3369
+ }
2956
3370
  }
2957
3371
  }
2958
- });
3372
+ title = title.trimEnd() + (fng.formsAngular.title.suffix || '');
3373
+ $window.document.title = title;
3374
+ }
2959
3375
  }
2960
3376
  }, true);
2961
3377
  if ($scope.id) {
2962
3378
  // Going to read a record
2963
- if (typeof $scope.dataEventFunctions.onBeforeRead === 'function') {
3379
+ if (typeof $scope.dataEventFunctions.onBeforeRead === "function") {
2964
3380
  $scope.dataEventFunctions.onBeforeRead($scope.id, function (err) {
2965
3381
  if (err) {
2966
3382
  $scope.showError(err);
@@ -2976,99 +3392,125 @@ var fng;
2976
3392
  }
2977
3393
  else {
2978
3394
  // New record
2979
- ctrlState.master = {};
3395
+ ctrlState.allowLocationChange = false;
3396
+ ctrlState.master = $scope.setDefaults($scope.formSchema);
2980
3397
  var passedRecord = $scope.initialiseNewRecord || $location.$$search.r;
2981
3398
  if (passedRecord) {
2982
3399
  try {
2983
- ctrlState.master = JSON.parse(passedRecord);
2984
- // Although this is a new record we are making it dirty from the url so we need to $setDirty
2985
- $scope.$on('fngCancel', function () {
2986
- setTimeout(function () {
2987
- if ($scope[$scope.topLevelFormName]) {
2988
- $scope[$scope.topLevelFormName].$setDirty();
2989
- }
2990
- }, 2); // Has to fire after the setPristime timeout.
2991
- });
3400
+ Object.assign(ctrlState.master, JSON.parse(passedRecord));
3401
+ if (!$scope["newRecordsStartPristine"]) {
3402
+ // Although this is a new record we are making it dirty from the url so we need to $setDirty
3403
+ $scope.$on("fngCancel", function () {
3404
+ $timeout(function () {
3405
+ if ($scope[$scope.topLevelFormName]) {
3406
+ $scope[$scope.topLevelFormName].$setDirty();
3407
+ }
3408
+ }, 1000); // Has to fire after the setPristime timeout.
3409
+ });
3410
+ }
2992
3411
  }
2993
3412
  catch (e) {
2994
- console.log('Error parsing specified record : ' + e.message);
3413
+ console.log("Error parsing specified record : " + e.message);
2995
3414
  }
2996
3415
  }
2997
- if (typeof $scope.dataEventFunctions.onInitialiseNewRecord === 'function') {
3416
+ if (typeof $scope.dataEventFunctions.onInitialiseNewRecord === "function") {
3417
+ console.log("onInitialiseNewRecord is deprecated - use the async version - onNewRecordInit(data,cb)");
2998
3418
  $scope.dataEventFunctions.onInitialiseNewRecord(ctrlState.master);
2999
3419
  }
3000
- $scope.phase = 'ready';
3001
- $scope.cancel();
3420
+ if (typeof $scope.dataEventFunctions.onNewRecordInit === "function") {
3421
+ $scope.dataEventFunctions.onNewRecordInit(ctrlState.master, function (err) {
3422
+ if (err) {
3423
+ $scope.showError(err);
3424
+ }
3425
+ else {
3426
+ notifyReady();
3427
+ }
3428
+ });
3429
+ }
3430
+ else {
3431
+ notifyReady();
3432
+ }
3002
3433
  }
3003
3434
  }
3004
3435
  }
3005
3436
  function handleError($scope) {
3006
3437
  return function (response) {
3007
3438
  if ([200, 400].indexOf(response.status) !== -1) {
3008
- var errorMessage = '';
3439
+ var errorMessage = "";
3009
3440
  for (var errorField in response.data.errors) {
3010
3441
  if (response.data.errors.hasOwnProperty(errorField)) {
3011
- errorMessage += '<li><b>' + $filter('titleCase')(errorField) + ': </b> ';
3442
+ errorMessage += "<li><b>" + $filter("titleCase")(errorField) + ": </b> ";
3012
3443
  switch (response.data.errors[errorField].type) {
3013
- case 'enum':
3014
- errorMessage += 'You need to select from the list of values';
3444
+ case "enum":
3445
+ errorMessage += "You need to select from the list of values";
3015
3446
  break;
3016
3447
  default:
3017
3448
  errorMessage += response.data.errors[errorField].message;
3018
3449
  break;
3019
3450
  }
3020
- errorMessage += '</li>';
3451
+ errorMessage += "</li>";
3021
3452
  }
3022
3453
  }
3023
3454
  if (errorMessage.length > 0) {
3024
- errorMessage = response.data.message + '<br /><ul>' + errorMessage + '</ul>';
3455
+ errorMessage = (response.data.message || response.data._message) + "<br /><ul>" + errorMessage + "</ul>";
3025
3456
  }
3026
3457
  else {
3027
- errorMessage = response.data.message || 'Error! Sorry - No further details available.';
3458
+ errorMessage = response.data.message || response.data._message || response.data.err || "Error! Sorry - No further details available.";
3028
3459
  }
3029
3460
  $scope.showError(errorMessage);
3030
3461
  }
3031
3462
  else {
3032
- $scope.showError(response.status + ' ' + JSON.stringify(response.data));
3463
+ $scope.showError(response.status + " " + JSON.stringify(response.data));
3033
3464
  }
3034
3465
  };
3035
3466
  }
3036
3467
  function handleIncomingData(data, $scope, ctrlState) {
3037
3468
  ctrlState.allowLocationChange = false;
3038
- $scope.phase = 'reading';
3039
- if (typeof $scope.dataEventFunctions.onAfterRead === 'function') {
3469
+ $scope.phase = "reading";
3470
+ if (typeof $scope.dataEventFunctions.onAfterRead === "function") {
3040
3471
  $scope.dataEventFunctions.onAfterRead(data);
3041
3472
  }
3042
3473
  $scope.originalData = data;
3043
3474
  processServerData(data, $scope, ctrlState);
3044
3475
  }
3476
+ function addArrayLookupToLookupList($scope, formInstructions, ref, lookups) {
3477
+ var nameElements = formInstructions.name.split(".");
3478
+ var refHandler = lookups.find(function (lkp) {
3479
+ return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3480
+ });
3481
+ var thisHandler = {
3482
+ formInstructions: formInstructions,
3483
+ lastPart: nameElements.pop(),
3484
+ possibleArray: nameElements.join(".")
3485
+ };
3486
+ if (!refHandler) {
3487
+ refHandler = {
3488
+ ref: ref,
3489
+ lookupOptions: [],
3490
+ lookupIds: [],
3491
+ handlers: []
3492
+ };
3493
+ lookups.push(refHandler);
3494
+ }
3495
+ refHandler.handlers.push(thisHandler);
3496
+ $scope[formInstructions.options] = refHandler.lookupOptions;
3497
+ $scope[formInstructions.ids] = refHandler.lookupIds;
3498
+ }
3045
3499
  return {
3046
3500
  readRecord: function readRecord($scope, ctrlState) {
3047
- // TODO Consider using $parse for this - http://bahmutov.calepin.co/angularjs-parse-hacks.html
3048
- SubmissionsService.readRecord($scope.modelName, $scope.id)
3501
+ $scope.readingRecord = SubmissionsService.readRecord($scope.modelName, $scope.id);
3502
+ $scope.readingRecord
3049
3503
  .then(function (response) {
3050
- var data = response.data;
3051
- if (data.success === false) {
3052
- $location.path('/404');
3053
- // TODO Figure out tab history updates (check for other tab-history-todos)
3054
- // } else if (response.master) {
3055
- //
3056
- // ctrlState.allowLocationChange = false;
3057
- // $scope.phase = 'ready';
3058
- // $scope.record = angular.copy(response.data);
3059
- // ctrlState.master = angular.copy(response.master);
3060
- // if (response.changed) {
3061
- // $timeout(() => {
3062
- // $scope[$scope.topLevelFormName].$setDirty();
3063
- // });
3064
- // } else {
3065
- // $timeout($scope.setPristine);
3066
- // }
3504
+ var data = angular.copy(response.data);
3505
+ handleIncomingData(data, $scope, ctrlState);
3506
+ }, function (error) {
3507
+ if (error.status === 404) {
3508
+ $location.path("/404");
3067
3509
  }
3068
3510
  else {
3069
- handleIncomingData(data, $scope, ctrlState);
3511
+ $scope.handleHttpError(error);
3070
3512
  }
3071
- }, $scope.handleHttpError);
3513
+ });
3072
3514
  },
3073
3515
  scrollTheList: function scrollTheList($scope) {
3074
3516
  var pagesLoaded = $scope.pagesLoaded;
@@ -3088,31 +3530,38 @@ var fng;
3088
3530
  $scope.recordList = $scope.recordList.concat(data);
3089
3531
  }
3090
3532
  else {
3091
- console.log('DEBUG: infinite scroll component asked for a page twice');
3533
+ console.log("DEBUG: infinite scroll component asked for a page twice - the model was " + $scope.modelName);
3092
3534
  }
3093
3535
  }
3094
3536
  else {
3095
- $scope.showError(data, 'Invalid query');
3537
+ $scope.showError(data, "Invalid query");
3096
3538
  }
3097
3539
  }, $scope.handleHttpError);
3098
3540
  },
3099
- // TODO: Do we need model here? Can we not infer it from scope?
3100
- deleteRecord: function deleteRecord(model, id, $scope, ctrlState) {
3101
- SubmissionsService.deleteRecord(model, id)
3541
+ deleteRecord: function deleteRecord(id, $scope, ctrlState) {
3542
+ SubmissionsService.deleteRecord($scope.modelName, id)
3102
3543
  .then(function () {
3103
- if (typeof $scope.dataEventFunctions.onAfterDelete === 'function') {
3544
+ if (typeof $scope.dataEventFunctions.onAfterDelete === "function") {
3104
3545
  $scope.dataEventFunctions.onAfterDelete(ctrlState.master);
3105
3546
  }
3106
- routingService.redirectTo()('list', $scope, $location);
3547
+ routingService.redirectTo()("onDelete", $scope, $location);
3548
+ }, function (err) {
3549
+ if (err.status === 404) {
3550
+ // Someone already deleted it
3551
+ routingService.redirectTo()("onDelete", $scope, $location);
3552
+ }
3553
+ else {
3554
+ $scope.showError("".concat(err.statusText, " (").concat(err.status, ") while deleting record<br />").concat(err.data), 'Error deleting record');
3555
+ }
3107
3556
  });
3108
3557
  },
3109
3558
  updateDocument: function updateDocument(dataToSave, options, $scope, ctrlState) {
3110
- $scope.phase = 'updating';
3559
+ $scope.phase = "updating";
3111
3560
  SubmissionsService.updateRecord($scope.modelName, $scope.id, dataToSave)
3112
3561
  .then(function (response) {
3113
3562
  var data = response.data;
3114
3563
  if (data.success !== false) {
3115
- if (typeof $scope.dataEventFunctions.onAfterUpdate === 'function') {
3564
+ if (typeof $scope.dataEventFunctions.onAfterUpdate === "function") {
3116
3565
  $scope.dataEventFunctions.onAfterUpdate(data, ctrlState.master);
3117
3566
  }
3118
3567
  if (options.redirect) {
@@ -3131,19 +3580,20 @@ var fng;
3131
3580
  }
3132
3581
  }, $scope.handleHttpError);
3133
3582
  },
3134
- createNew: function createNew(dataToSave, options, $scope) {
3583
+ createNew: function createNew(dataToSave, options, $scope, ctrlState) {
3135
3584
  SubmissionsService.createRecord($scope.modelName, dataToSave)
3136
3585
  .then(function (response) {
3137
3586
  var data = response.data;
3138
3587
  if (data.success !== false) {
3139
- if (typeof $scope.dataEventFunctions.onAfterCreate === 'function') {
3588
+ ctrlState.allowLocationChange = true;
3589
+ if (typeof $scope.dataEventFunctions.onAfterCreate === "function") {
3140
3590
  $scope.dataEventFunctions.onAfterCreate(data);
3141
3591
  }
3142
3592
  if (options.redirect) {
3143
3593
  $window.location = options.redirect;
3144
3594
  }
3145
3595
  else {
3146
- routingService.redirectTo()('edit', $scope, $location, data._id);
3596
+ routingService.redirectTo()("edit", $scope, $location, data._id);
3147
3597
  }
3148
3598
  }
3149
3599
  else {
@@ -3161,9 +3611,9 @@ var fng;
3161
3611
  .then(function (response) {
3162
3612
  var data = response.data;
3163
3613
  var listInstructions = [];
3164
- handleSchema('Lookup ' + lookupCollection, data, null, listInstructions, '', false, $scope, ctrlState);
3614
+ handleSchema("Lookup " + lookupCollection, data, null, listInstructions, "", false, $scope, ctrlState);
3165
3615
  var dataRequest;
3166
- if (typeof schemaElement.filter !== 'undefined' && schemaElement.filter) {
3616
+ if (typeof schemaElement.filter !== "undefined" && schemaElement.filter) {
3167
3617
  dataRequest = SubmissionsService.getPagedAndFilteredList(lookupCollection, schemaElement.filter);
3168
3618
  }
3169
3619
  else {
@@ -3171,25 +3621,30 @@ var fng;
3171
3621
  }
3172
3622
  dataRequest
3173
3623
  .then(function (response) {
3174
- var data = response.data;
3624
+ var data = angular.copy(response.data);
3175
3625
  if (data) {
3176
3626
  for (var i = 0; i < data.length; i++) {
3177
- var option = '';
3627
+ var option = "";
3178
3628
  for (var j = 0; j < listInstructions.length; j++) {
3179
3629
  var thisVal = data[i][listInstructions[j].name];
3180
- option += thisVal ? thisVal + ' ' : '';
3630
+ option += thisVal ? thisVal + " " : "";
3181
3631
  }
3182
3632
  option = option.trim();
3183
3633
  var pos = _.sortedIndex(optionsList, option);
3184
3634
  // handle dupes (ideally people will use unique indexes to stop them but...)
3185
3635
  if (optionsList[pos] === option) {
3186
- option = option + ' (' + data[i]._id + ')';
3636
+ option = option + " (" + data[i]._id + ")";
3187
3637
  pos = _.sortedIndex(optionsList, option);
3188
3638
  }
3189
3639
  optionsList.splice(pos, 0, option);
3190
3640
  idList.splice(pos, 0, data[i]._id);
3191
3641
  }
3192
- updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
3642
+ if ($scope.readingRecord) {
3643
+ $scope.readingRecord
3644
+ .then(function () {
3645
+ updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
3646
+ });
3647
+ }
3193
3648
  }
3194
3649
  });
3195
3650
  });
@@ -3197,31 +3652,9 @@ var fng;
3197
3652
  setUpLookupListOptions: function setUpLookupListOptions(ref, formInstructions, $scope, ctrlState) {
3198
3653
  var optionsList = $scope[formInstructions.options] = [];
3199
3654
  var idList = $scope[formInstructions.ids] = [];
3200
- if (ref.id[0] === '$') {
3201
- // id of document we are doing lookup from comes from record, so we need to deal with in $watch
3202
- // by adding it to listLookups
3203
- var nameElements = formInstructions.name.split('.');
3204
- var refHandler = $scope.listLookups.find(function (lkp) {
3205
- return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3206
- });
3207
- var thisHandler = {
3208
- formInstructions: formInstructions,
3209
- lastPart: nameElements.pop(),
3210
- possibleArray: nameElements.join('.')
3211
- };
3212
- if (!refHandler) {
3213
- refHandler = {
3214
- ref: ref,
3215
- lookupOptions: [],
3216
- lookupIds: [],
3217
- handlers: []
3218
- };
3219
- $scope.listLookups.push(refHandler);
3220
- }
3221
- refHandler.handlers.push(thisHandler);
3222
- $scope[formInstructions.options] = refHandler.lookupOptions;
3223
- $scope[formInstructions.ids] = refHandler.lookupIds;
3224
- // TODO DRY this and handleInternalLookup below
3655
+ if (ref.id[0] === "$") {
3656
+ // id of document that contains out lookup list comes from record, so we need to deal with in $watch by adding it to listLookups
3657
+ addArrayLookupToLookupList($scope, formInstructions, ref, $scope.listLookups);
3225
3658
  }
3226
3659
  else {
3227
3660
  // we can do it now
@@ -3232,7 +3665,7 @@ var fng;
3232
3665
  var pos = _.sortedIndex(optionsList, option);
3233
3666
  // handle dupes
3234
3667
  if (optionsList[pos] === option) {
3235
- option = option + ' (' + data[i]._id + ')';
3668
+ option = option + " (" + data[i]._id + ")";
3236
3669
  pos = _.sortedIndex(optionsList, option);
3237
3670
  }
3238
3671
  optionsList.splice(pos, 0, option);
@@ -3243,27 +3676,7 @@ var fng;
3243
3676
  }
3244
3677
  },
3245
3678
  handleInternalLookup: function handleInternalLookup($scope, formInstructions, ref) {
3246
- var nameElements = formInstructions.name.split('.');
3247
- var refHandler = $scope.internalLookups.find(function (lkp) {
3248
- return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3249
- });
3250
- var thisHandler = {
3251
- formInstructions: formInstructions,
3252
- lastPart: nameElements.pop(),
3253
- possibleArray: nameElements.join('.')
3254
- };
3255
- if (!refHandler) {
3256
- refHandler = {
3257
- ref: ref,
3258
- lookupOptions: [],
3259
- lookupIds: [],
3260
- handlers: []
3261
- };
3262
- $scope.internalLookups.push(refHandler);
3263
- }
3264
- refHandler.handlers.push(thisHandler);
3265
- $scope[formInstructions.options] = refHandler.lookupOptions;
3266
- $scope[formInstructions.ids] = refHandler.lookupIds;
3679
+ addArrayLookupToLookupList($scope, formInstructions, ref, $scope.internalLookups);
3267
3680
  },
3268
3681
  preservePristine: preservePristine,
3269
3682
  // Reverse the process of convertToAngularModel
@@ -3280,48 +3693,54 @@ var fng;
3280
3693
  }
3281
3694
  return retVal;
3282
3695
  }
3283
- for (var i = 0; i < schema.length; i++) {
3284
- var fieldname = schema[i].name.slice(prefixLength);
3285
- var thisField = getListData($scope, anObject, fieldname);
3286
- if (schema[i].schema) {
3696
+ var _loop_1 = function () {
3697
+ var schemaI = schema[i];
3698
+ var fieldname = schemaI.name.slice(prefixLength);
3699
+ var thisField = getListData(anObject, fieldname, null, $scope);
3700
+ if (schemaI.schema) {
3287
3701
  if (thisField) {
3288
3702
  for (var j = 0; j < thisField.length; j++) {
3289
- thisField[j] = convertToMongoModel(schema[i].schema, thisField[j], prefixLength + 1 + fieldname.length, $scope, fieldname);
3703
+ thisField[j] = convertToMongoModel(schemaI.schema, thisField[j], 1 + fieldname.length, $scope, fieldname);
3290
3704
  }
3291
3705
  }
3292
3706
  }
3293
3707
  else {
3294
3708
  // Convert {array:[{x:'item 1'}]} to {array:['item 1']}
3295
- if (schema[i].array && simpleArrayNeedsX(schema[i]) && thisField) {
3709
+ if (schemaI.array && simpleArrayNeedsX(schemaI) && thisField) {
3296
3710
  for (var k = 0; k < thisField.length; k++) {
3297
3711
  thisField[k] = thisField[k].x;
3298
3712
  }
3299
3713
  }
3300
3714
  // Convert {lookup:'List description for 012abcde'} to {lookup:'012abcde'}
3301
- var idList = $scope[suffixCleanId(schema[i], '_ids')];
3302
- var thisConversion = void 0;
3303
- if (idList && idList.length > 0) {
3715
+ var idList_1 = $scope[suffixCleanId(schemaI, "_ids")];
3716
+ if (idList_1 && idList_1.length > 0) {
3304
3717
  updateObject(fieldname, anObject, function (value) {
3305
- return convertToForeignKeys(schema[i], value, $scope[suffixCleanId(schema[i], 'Options')], idList);
3718
+ return convertToForeignKeys(schemaI, value, $scope[suffixCleanId(schemaI, "Options")], idList_1);
3306
3719
  });
3307
3720
  }
3308
- else if (thisConversion = getConversionObject($scope, fieldname, schemaName)) {
3309
- var lookup = getData(anObject, fieldname, null);
3310
- var newVal;
3311
- if (schema[i].array) {
3312
- newVal = [];
3313
- if (lookup) {
3314
- for (var n = 0; n < lookup.length; n++) {
3315
- newVal[n] = convertLookup(lookup[n], thisConversion);
3721
+ else {
3722
+ var thisConversion = getConversionObject($scope, fieldname, schemaName);
3723
+ if (thisConversion) {
3724
+ var lookup = getData(anObject, fieldname, null);
3725
+ var newVal = void 0;
3726
+ if (schemaI.array) {
3727
+ newVal = [];
3728
+ if (lookup) {
3729
+ for (var n = 0; n < lookup.length; n++) {
3730
+ newVal[n] = convertLookup(lookup[n], thisConversion);
3731
+ }
3316
3732
  }
3317
3733
  }
3734
+ else {
3735
+ newVal = convertLookup(lookup, thisConversion);
3736
+ }
3737
+ setData(anObject, fieldname, null, newVal);
3318
3738
  }
3319
- else {
3320
- newVal = convertLookup(lookup, thisConversion);
3321
- }
3322
- setData(anObject, fieldname, null, newVal);
3323
3739
  }
3324
3740
  }
3741
+ };
3742
+ for (var i = 0; i < schema.length; i++) {
3743
+ _loop_1();
3325
3744
  }
3326
3745
  return anObject;
3327
3746
  },
@@ -3331,7 +3750,7 @@ var fng;
3331
3750
  $scope.handleHttpError = handleError($scope);
3332
3751
  $scope.cancel = function () {
3333
3752
  angular.copy(ctrlState.master, $scope.record);
3334
- $scope.$broadcast('fngCancel', $scope);
3753
+ $scope.$broadcast("fngCancel", $scope);
3335
3754
  // Let call backs etc resolve in case they dirty form, then clean it
3336
3755
  $timeout($scope.setPristine);
3337
3756
  };
@@ -3340,15 +3759,21 @@ var fng;
3340
3759
  // scope.$emit('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
3341
3760
  // or
3342
3761
  // scope.$broadcast('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
3343
- $scope.$on('showErrorMessage', function (event, args) {
3344
- $scope.showError(args.body, args.title);
3762
+ $scope.$on("showErrorMessage", function (event, args) {
3763
+ if (!event.defaultPrevented) {
3764
+ event.defaultPrevented = true;
3765
+ $scope.showError(args.body, args.title);
3766
+ }
3345
3767
  });
3346
3768
  $scope.showError = function (error, alertTitle) {
3347
- $scope.alertTitle = alertTitle ? alertTitle : 'Error!';
3348
- if (typeof error === 'string') {
3769
+ $scope.alertTitle = alertTitle ? alertTitle : "Error!";
3770
+ if (typeof error === "string") {
3349
3771
  $scope.errorMessage = error;
3350
3772
  }
3351
- else if (error.message && typeof error.message === 'string') {
3773
+ else if (!error) {
3774
+ $scope.errorMessage = "An error occurred - that's all we got. Sorry.";
3775
+ }
3776
+ else if (error.message && typeof error.message === "string") {
3352
3777
  $scope.errorMessage = error.message;
3353
3778
  }
3354
3779
  else if (error.data && error.data.message) {
@@ -3362,16 +3787,35 @@ var fng;
3362
3787
  $scope.errorMessage = error;
3363
3788
  }
3364
3789
  }
3790
+ $scope.errorHideTimer = window.setTimeout(function () {
3791
+ $scope.dismissError();
3792
+ $scope.$digest();
3793
+ }, 3500 + (1000 * ($scope.alertTitle + $scope.errorMessage).length / 50));
3794
+ $scope.errorVisible = true;
3795
+ window.setTimeout(function () {
3796
+ $scope.$digest();
3797
+ });
3798
+ };
3799
+ $scope.clearTimeout = function () {
3800
+ if ($scope.errorHideTimer) {
3801
+ clearTimeout($scope.errorHideTimer);
3802
+ delete $scope.errorHideTimer;
3803
+ }
3365
3804
  };
3366
3805
  $scope.dismissError = function () {
3806
+ $scope.clearTimeout;
3807
+ $scope.errorVisible = false;
3367
3808
  delete $scope.errorMessage;
3368
3809
  delete $scope.alertTitle;
3369
3810
  };
3811
+ $scope.stickError = function () {
3812
+ clearTimeout($scope.errorHideTimer);
3813
+ };
3370
3814
  $scope.prepareForSave = function (cb) {
3371
3815
  //Convert the lookup values into ids
3372
3816
  var dataToSave = recordHandlerInstance.convertToMongoModel($scope.formSchema, angular.copy($scope.record), 0, $scope);
3373
3817
  if ($scope.id) {
3374
- if (typeof $scope.dataEventFunctions.onBeforeUpdate === 'function') {
3818
+ if (typeof $scope.dataEventFunctions.onBeforeUpdate === "function") {
3375
3819
  $scope.dataEventFunctions.onBeforeUpdate(dataToSave, ctrlState.master, function (err) {
3376
3820
  if (err) {
3377
3821
  cb(err);
@@ -3386,7 +3830,7 @@ var fng;
3386
3830
  }
3387
3831
  }
3388
3832
  else {
3389
- if (typeof $scope.dataEventFunctions.onBeforeCreate === 'function') {
3833
+ if (typeof $scope.dataEventFunctions.onBeforeCreate === "function") {
3390
3834
  $scope.dataEventFunctions.onBeforeCreate(dataToSave, function (err) {
3391
3835
  if (err) {
3392
3836
  cb(err);
@@ -3405,22 +3849,24 @@ var fng;
3405
3849
  options = options || {};
3406
3850
  $scope.prepareForSave(function (err, dataToSave) {
3407
3851
  if (err) {
3408
- if (err !== '_update_handled_') {
3409
- $scope.showError(err);
3852
+ if (err !== "_update_handled_") {
3853
+ $timeout(function () {
3854
+ $scope.showError(err);
3855
+ });
3410
3856
  }
3411
3857
  }
3412
3858
  else if ($scope.id) {
3413
3859
  recordHandlerInstance.updateDocument(dataToSave, options, $scope, ctrlState);
3414
3860
  }
3415
3861
  else {
3416
- recordHandlerInstance.createNew(dataToSave, options, $scope);
3862
+ recordHandlerInstance.createNew(dataToSave, options, $scope, ctrlState);
3417
3863
  }
3418
3864
  });
3419
3865
  };
3420
3866
  $scope.newClick = function () {
3421
- routingService.redirectTo()('new', $scope, $location);
3867
+ routingService.redirectTo()("new", $scope, $location);
3422
3868
  };
3423
- $scope.$on('$locationChangeStart', function (event, next) {
3869
+ $scope.$on("$locationChangeStart", function (event, next) {
3424
3870
  // let changed = !$scope.isCancelDisabled();
3425
3871
  // let curPath = window.location.href.split('/');
3426
3872
  // let nextPath = next.split('/');
@@ -3439,21 +3885,12 @@ var fng;
3439
3885
  if (!ctrlState.allowLocationChange && !$scope.isCancelDisabled()) {
3440
3886
  event.preventDefault();
3441
3887
  var modalInstance = $uibModal.open({
3442
- template: '<div class="modal-header">' +
3443
- ' <h3>Record modified</h3>' +
3444
- '</div>' +
3445
- '<div class="modal-body">' +
3446
- ' <p>Would you like to save your changes?</p>' +
3447
- '</div>' +
3448
- '<div class="modal-footer">' +
3449
- ' <button class="btn btn-primary dlg-yes" ng-click="yes()">Yes</button>' +
3450
- ' <button class="btn btn-warning dlg-no" ng-click="no()">No</button>' +
3451
- ' <button class="btn dlg-cancel" ng-click="cancel()">Cancel</button>' +
3452
- '</div>',
3453
- controller: 'SaveChangesModalCtrl',
3454
- backdrop: 'static'
3888
+ template: "<div class=\"modal-header\">\n <h3>Record modified</h3>\n</div>\n<div class=\"modal-body\">\n <p>Would you like to save your changes?</p>\n</div>\n<div class=\"modal-footer\">\n <button class=\"btn btn-primary dlg-yes\" ng-click=\"yes()\">Yes</button>\n <button class=\"btn btn-warning dlg-no\" ng-click=\"no()\">No</button>\n <button class=\"btn dlg-cancel\" ng-click=\"cancel()\">Cancel</button>\n</div>",
3889
+ controller: "SaveChangesModalCtrl",
3890
+ backdrop: "static"
3455
3891
  });
3456
- modalInstance.result.then(function (result) {
3892
+ modalInstance.result
3893
+ .then(function (result) {
3457
3894
  if (result) {
3458
3895
  $scope.save({ redirect: next, allowChange: true }); // save changes
3459
3896
  }
@@ -3461,7 +3898,8 @@ var fng;
3461
3898
  ctrlState.allowLocationChange = true;
3462
3899
  $window.location = next;
3463
3900
  }
3464
- });
3901
+ })
3902
+ .catch(_handleCancel);
3465
3903
  }
3466
3904
  });
3467
3905
  $scope.deleteClick = function () {
@@ -3472,85 +3910,176 @@ var fng;
3472
3910
  }
3473
3911
  else {
3474
3912
  var modalInstance = $uibModal.open({
3475
- template: '<div class="modal-header">' +
3476
- ' <h3>Delete Item</h3>' +
3477
- '</div>' +
3478
- '<div class="modal-body">' +
3479
- ' <p>Are you sure you want to delete this record?</p>' +
3480
- '</div>' +
3481
- '<div class="modal-footer">' +
3482
- ' <button class="btn btn-primary dlg-no" ng-click="cancel()">No</button>' +
3483
- ' <button class="btn btn-warning dlg-yes" ng-click="yes()">Yes</button>' +
3484
- '</div>',
3485
- controller: 'SaveChangesModalCtrl',
3486
- backdrop: 'static'
3913
+ template: "<div class=\"modal-header\">\n <h3>Delete Item</h3>\n</div>\n<div class=\"modal-body\">\n <p>Are you sure you want to delete this record?</p>\n</div>\n<div class=\"modal-footer\">\n <button class=\"btn btn-primary dlg-no\" ng-click=\"cancel()\">No</button>\n <button class=\"btn btn-warning dlg-yes\" ng-click=\"yes()\">Yes</button>\n</div>",
3914
+ controller: "SaveChangesModalCtrl",
3915
+ backdrop: "static"
3487
3916
  });
3488
3917
  confirmDelete = modalInstance.result;
3489
3918
  }
3490
3919
  confirmDelete.then(function (result) {
3920
+ function doTheDeletion() {
3921
+ recordHandlerInstance.deleteRecord($scope.id, $scope, ctrlState);
3922
+ }
3491
3923
  if (result) {
3492
- if (typeof $scope.dataEventFunctions.onBeforeDelete === 'function') {
3924
+ if (typeof $scope.dataEventFunctions.onBeforeDelete === "function") {
3493
3925
  $scope.dataEventFunctions.onBeforeDelete(ctrlState.master, function (err) {
3494
3926
  if (err) {
3495
- if (err !== '_delete_handled_') {
3927
+ if (err !== "_delete_handled_") {
3496
3928
  $scope.showError(err);
3497
3929
  }
3498
3930
  }
3499
3931
  else {
3500
- recordHandlerInstance.deleteRecord($scope.modelName, $scope.id, $scope, ctrlState);
3932
+ doTheDeletion();
3501
3933
  }
3502
3934
  });
3503
3935
  }
3504
3936
  else {
3505
- recordHandlerInstance.deleteRecord($scope.modelName, $scope.id, $scope, ctrlState);
3937
+ doTheDeletion();
3506
3938
  }
3507
3939
  }
3508
- });
3940
+ })
3941
+ .catch(_handleCancel);
3509
3942
  }
3510
3943
  };
3511
3944
  $scope.isCancelDisabled = function () {
3512
- if (typeof $scope.disableFunctions.isCancelDisabled === 'function') {
3945
+ if ($scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine) {
3946
+ return true;
3947
+ }
3948
+ else if (typeof $scope.disableFunctions.isCancelDisabled === "function") {
3513
3949
  return $scope.disableFunctions.isCancelDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3514
3950
  }
3515
3951
  else {
3516
- return $scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine;
3952
+ return false;
3517
3953
  }
3518
3954
  };
3519
3955
  $scope.isSaveDisabled = function () {
3520
- if (typeof $scope.disableFunctions.isSaveDisabled === 'function') {
3521
- return $scope.disableFunctions.isSaveDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3956
+ $scope.whyDisabled = undefined;
3957
+ var pristine = false;
3958
+ function generateWhyDisabledMessage(form, subFormName) {
3959
+ form.$$controls.forEach(function (c) {
3960
+ if (c.$invalid) {
3961
+ if (c.$$controls) {
3962
+ // nested form
3963
+ generateWhyDisabledMessage(c, c.$name);
3964
+ }
3965
+ else {
3966
+ $scope.whyDisabled += "<br /><strong>";
3967
+ if (subFormName) {
3968
+ $scope.whyDisabled += subFormName + ' ';
3969
+ }
3970
+ if (cssFrameworkService.framework() === "bs2" &&
3971
+ c.$$element &&
3972
+ c.$$element.parent() &&
3973
+ c.$$element.parent().parent() &&
3974
+ c.$$element.parent().parent().find("label") &&
3975
+ c.$$element.parent().parent().find("label").text()) {
3976
+ $scope.whyDisabled += c.$$element.parent().parent().find("label").text();
3977
+ }
3978
+ else if (cssFrameworkService.framework() === "bs3" &&
3979
+ c.$$element &&
3980
+ c.$$element.parent() &&
3981
+ c.$$element.parent().parent() &&
3982
+ c.$$element.parent().parent().parent() &&
3983
+ c.$$element.parent().parent().parent().find("label") &&
3984
+ c.$$element.parent().parent().parent().find("label").text()) {
3985
+ $scope.whyDisabled += c.$$element.parent().parent().parent().find("label").text();
3986
+ }
3987
+ else {
3988
+ $scope.whyDisabled += c.$name;
3989
+ }
3990
+ $scope.whyDisabled += "</strong>: ";
3991
+ if (c.$error) {
3992
+ for (var type in c.$error) {
3993
+ if (c.$error.hasOwnProperty(type)) {
3994
+ switch (type) {
3995
+ case "required":
3996
+ $scope.whyDisabled += "Field missing required value. ";
3997
+ break;
3998
+ case "pattern":
3999
+ $scope.whyDisabled += "Field does not match required pattern. ";
4000
+ break;
4001
+ default:
4002
+ $scope.whyDisabled += type + ". ";
4003
+ }
4004
+ }
4005
+ }
4006
+ }
4007
+ }
4008
+ }
4009
+ });
4010
+ }
4011
+ if ($scope[$scope.topLevelFormName]) {
4012
+ if ($scope[$scope.topLevelFormName].$invalid) {
4013
+ $scope.whyDisabled = 'The form data is invalid:';
4014
+ generateWhyDisabledMessage($scope[$scope.topLevelFormName]);
4015
+ }
4016
+ else if ($scope[$scope.topLevelFormName].$pristine) {
4017
+ // Don't have disabled message - should be obvious from Cancel being disabled,
4018
+ // and the message comes up when the Save button is clicked.
4019
+ pristine = true;
4020
+ }
4021
+ }
4022
+ else {
4023
+ $scope.whyDisabled = "Top level form name invalid";
4024
+ }
4025
+ if (pristine || !!$scope.whyDisabled) {
4026
+ return true;
4027
+ }
4028
+ else if (typeof $scope.disableFunctions.isSaveDisabled !== "function") {
4029
+ return false;
3522
4030
  }
3523
4031
  else {
3524
- return ($scope[$scope.topLevelFormName] && ($scope[$scope.topLevelFormName].$invalid || $scope[$scope.topLevelFormName].$pristine));
4032
+ var retVal = $scope.disableFunctions.isSaveDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
4033
+ if (typeof retVal === "string") {
4034
+ $scope.whyDisabled = retVal;
4035
+ }
4036
+ else {
4037
+ $scope.whyDisabled = "An application level user-specified function is inhibiting saving the record";
4038
+ }
4039
+ return !!retVal;
3525
4040
  }
3526
4041
  };
3527
4042
  $scope.isDeleteDisabled = function () {
3528
- if (typeof $scope.disableFunctions.isDeleteDisabled === 'function') {
4043
+ if (!$scope.id) {
4044
+ return true;
4045
+ }
4046
+ else if (typeof $scope.disableFunctions.isDeleteDisabled === "function") {
3529
4047
  return $scope.disableFunctions.isDeleteDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3530
4048
  }
3531
4049
  else {
3532
- return (!$scope.id);
4050
+ return false;
3533
4051
  }
3534
4052
  };
3535
4053
  $scope.isNewDisabled = function () {
3536
- if (typeof $scope.disableFunctions.isNewDisabled === 'function') {
4054
+ if (typeof $scope.disableFunctions.isNewDisabled === "function") {
3537
4055
  return $scope.disableFunctions.isNewDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3538
4056
  }
3539
4057
  else {
3540
4058
  return false;
3541
4059
  }
3542
4060
  };
3543
- $scope.disabledText = function (localStyling) {
3544
- var text = '';
3545
- if ($scope.isSaveDisabled) {
3546
- text = 'This button is only enabled when the form is complete and valid. Make sure all required inputs are filled in. ' + localStyling;
3547
- }
3548
- return text;
4061
+ $scope.setDefaults = function (formSchema, base) {
4062
+ if (base === void 0) { base = ''; }
4063
+ var retVal = {};
4064
+ formSchema.forEach(function (s) {
4065
+ if (s.defaultValue !== undefined) {
4066
+ var nameParts = s.name.replace(base, '').split(".");
4067
+ var target = retVal;
4068
+ for (var i = 0; i < nameParts.length - 1; i++) {
4069
+ if (!target[nameParts[i]]) {
4070
+ target[nameParts[i]] = {};
4071
+ }
4072
+ target = target[nameParts[i]];
4073
+ }
4074
+ target[nameParts[nameParts.length - 1]] = s.defaultValue;
4075
+ }
4076
+ });
4077
+ return retVal;
3549
4078
  };
3550
4079
  $scope.getVal = function (expression, index) {
3551
- if (expression.indexOf('$index') === -1 || typeof index !== 'undefined') {
4080
+ if (expression.indexOf("$index") === -1 || typeof index !== "undefined") {
3552
4081
  expression = expression.replace(/\$index/g, index);
3553
- return $scope.$eval('record.' + expression);
4082
+ return $scope.$eval("record." + expression);
3554
4083
  }
3555
4084
  //else {
3556
4085
  // Used to show error here, but angular seems to call before record is populated sometimes
@@ -3564,6 +4093,23 @@ var fng;
3564
4093
  }
3565
4094
  }
3566
4095
  };
4096
+ $scope.setUpCustomLookupOptions = function (schemaElement, ids, options, baseScope) {
4097
+ for (var _i = 0, _a = [$scope, baseScope]; _i < _a.length; _i++) {
4098
+ var scope = _a[_i];
4099
+ if (scope) {
4100
+ // need to be accessible on our scope for generation of the select options, and - for nested schemas -
4101
+ // on baseScope for the conversion back to ids done by prepareForSave
4102
+ scope[schemaElement.ids] = ids;
4103
+ scope[schemaElement.options] = options;
4104
+ }
4105
+ }
4106
+ var data = getData($scope.record, schemaElement.name);
4107
+ if (!data) {
4108
+ return;
4109
+ }
4110
+ data = convertForeignKeys(schemaElement, data, options, ids);
4111
+ setData($scope.record, schemaElement.name, undefined, data);
4112
+ };
3567
4113
  },
3568
4114
  fillFormFromBackendCustomSchema: fillFormFromBackendCustomSchema,
3569
4115
  fillFormWithBackendSchema: function fillFormWithBackendSchema($scope, formGeneratorInstance, recordHandlerInstance, ctrlState) {
@@ -3596,13 +4142,45 @@ var fng;
3596
4142
  })(services = fng.services || (fng.services = {}));
3597
4143
  })(fng || (fng = {}));
3598
4144
  /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
4145
+ var ExpirationCache = /** @class */ (function () {
4146
+ function ExpirationCache(timeout) {
4147
+ if (timeout === void 0) { timeout = 60 * 1000; }
4148
+ this.store = new Map();
4149
+ this.timeout = timeout;
4150
+ }
4151
+ ExpirationCache.prototype.get = function (key) {
4152
+ // this.store.has(key) ? console.log(`cache hit`) : console.log(`cache miss`);
4153
+ return this.store.get(key);
4154
+ };
4155
+ ExpirationCache.prototype.put = function (key, val) {
4156
+ var _this = this;
4157
+ this.store.set(key, val);
4158
+ // remove it once it's expired
4159
+ setTimeout(function () {
4160
+ // console.log(`removing expired key ${key}`);
4161
+ _this.remove(key);
4162
+ }, this.timeout);
4163
+ };
4164
+ ExpirationCache.prototype.remove = function (key) {
4165
+ this.store.delete(key);
4166
+ };
4167
+ ExpirationCache.prototype.removeAll = function () {
4168
+ this.store = new Map();
4169
+ };
4170
+ ExpirationCache.prototype.delete = function () {
4171
+ //no op here because this is standalone, not a part of $cacheFactory
4172
+ };
4173
+ return ExpirationCache;
4174
+ }());
3599
4175
  var fng;
3600
4176
  (function (fng) {
3601
4177
  var services;
3602
4178
  (function (services) {
3603
4179
  /*@ngInject*/
3604
- SubmissionsService.$inject = ["$http", "$cacheFactory"];
3605
- function SubmissionsService($http, $cacheFactory) {
4180
+ SubmissionsService.$inject = ["$http"];
4181
+ function SubmissionsService($http) {
4182
+ var useCacheForGetAll = true;
4183
+ var expCache = new ExpirationCache();
3606
4184
  /*
3607
4185
  generate a query string for a filtered and paginated query for submissions.
3608
4186
  options consists of the following:
@@ -3660,11 +4238,11 @@ var fng;
3660
4238
  // };
3661
4239
  // },
3662
4240
  getListAttributes: function (ref, id) {
3663
- if (typeof ref === 'string') {
3664
- ref = { type: 'lookup', collection: ref };
3665
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(ref));
4241
+ var actualId = typeof id === "string" ? id : id.id || id._id || id.x || id;
4242
+ if (typeof actualId !== "string") {
4243
+ throw new Error("getListAttributes requires a string id but was provided with ".concat(JSON.stringify(id)));
3666
4244
  }
3667
- return $http.get('/api/' + ref.collection + '/' + id + '/list');
4245
+ return $http.get('/api/' + ref + '/' + actualId + '/list', { cache: expCache });
3668
4246
  },
3669
4247
  readRecord: function (modelName, id) {
3670
4248
  // TODO Figure out tab history updates (check for other tab-history-todos)
@@ -3680,7 +4258,7 @@ var fng;
3680
4258
  },
3681
4259
  getAll: function (modelName, _options) {
3682
4260
  var options = angular.extend({
3683
- cache: true
4261
+ cache: useCacheForGetAll ? expCache : false
3684
4262
  }, _options);
3685
4263
  return $http.get('/api/' + modelName, options);
3686
4264
  },
@@ -3691,12 +4269,21 @@ var fng;
3691
4269
  return $http.delete('/api/' + model + '/' + id);
3692
4270
  },
3693
4271
  updateRecord: function (modelName, id, dataToSave) {
3694
- $cacheFactory.get('$http').remove('/api/' + modelName);
4272
+ expCache.remove('/api/' + modelName);
3695
4273
  return $http.post('/api/' + modelName + '/' + id, dataToSave);
3696
4274
  },
3697
4275
  createRecord: function (modelName, dataToSave) {
3698
- $cacheFactory.get('$http').remove('/api/' + modelName);
4276
+ expCache.remove('/api/' + modelName);
3699
4277
  return $http.post('/api/' + modelName, dataToSave);
4278
+ },
4279
+ useCache: function (val) {
4280
+ useCacheForGetAll = val;
4281
+ },
4282
+ getCache: function () {
4283
+ return !!expCache;
4284
+ },
4285
+ clearCache: function () {
4286
+ expCache.removeAll();
3700
4287
  }
3701
4288
  };
3702
4289
  }
@@ -3722,6 +4309,7 @@ var fng;
3722
4309
  fngInvalidRequired: 'fng-invalid-required',
3723
4310
  allowLocationChange: true // Set when the data arrives..
3724
4311
  };
4312
+ $scope.errorVisible = false;
3725
4313
  angular.extend($scope, routingService.parsePathFunc()($location.$$path));
3726
4314
  // Load context menu. For /person/client/:id/edit we need
3727
4315
  // to load PersonCtrl and PersonClientCtrl
@@ -3736,17 +4324,33 @@ var fng;
3736
4324
  $rootScope.$broadcast('fngFormLoadStart', $scope);
3737
4325
  formGenerator.decorateScope($scope, formGenerator, recordHandler, $scope.sharedData);
3738
4326
  recordHandler.decorateScope($scope, $uibModal, recordHandler, ctrlState);
3739
- recordHandler.fillFormWithBackendSchema($scope, formGenerator, recordHandler, ctrlState);
3740
- // Tell the 'model controllers' that they can start fiddling with basescope
3741
- for (var i = 0; i < $scope.sharedData.modelControllers.length; i++) {
3742
- if ($scope.sharedData.modelControllers[i].onBaseCtrlReady) {
3743
- $scope.sharedData.modelControllers[i].onBaseCtrlReady($scope);
4327
+ function processTheForm() {
4328
+ recordHandler.fillFormWithBackendSchema($scope, formGenerator, recordHandler, ctrlState);
4329
+ // Tell the 'model controllers' that they can start fiddling with baseScope
4330
+ for (var i = 0; i < $scope.sharedData.modelControllers.length; i++) {
4331
+ if ($scope.sharedData.modelControllers[i].onBaseCtrlReady) {
4332
+ $scope.sharedData.modelControllers[i].onBaseCtrlReady($scope);
4333
+ }
3744
4334
  }
4335
+ $scope.$on('$destroy', function () {
4336
+ $scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
4337
+ $rootScope.$broadcast('fngControllersUnloaded');
4338
+ });
4339
+ }
4340
+ //Check that we are ready
4341
+ if (typeof fng.formsAngular.beforeProcess === "function") {
4342
+ fng.formsAngular.beforeProcess($scope, function (err) {
4343
+ if (err) {
4344
+ $scope.showError(err.message, 'Error preparing to process form');
4345
+ }
4346
+ else {
4347
+ processTheForm();
4348
+ }
4349
+ });
4350
+ }
4351
+ else {
4352
+ processTheForm();
3745
4353
  }
3746
- $scope.$on('$destroy', function () {
3747
- $scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
3748
- $rootScope.$broadcast('fngControllersUnloaded');
3749
- });
3750
4354
  }
3751
4355
  controllers.BaseCtrl = BaseCtrl;
3752
4356
  })(controllers = fng.controllers || (fng.controllers = {}));
@@ -3802,13 +4406,17 @@ var fng;
3802
4406
  var controllers;
3803
4407
  (function (controllers) {
3804
4408
  /*@ngInject*/
3805
- NavCtrl.$inject = ["$scope", "$location", "$filter", "routingService", "cssFrameworkService"];
3806
- function NavCtrl($scope, $location, $filter, routingService, cssFrameworkService) {
4409
+ NavCtrl.$inject = ["$rootScope", "$window", "$scope", "$location", "$filter", "routingService", "cssFrameworkService"];
4410
+ function NavCtrl($rootScope, $window, $scope, $location, $filter, routingService, cssFrameworkService) {
3807
4411
  function clearContextMenu() {
3808
4412
  $scope.items = [];
3809
4413
  $scope.contextMenu = undefined;
3810
4414
  }
4415
+ $rootScope.navScope = $scope; // Lets plugins access menus
3811
4416
  clearContextMenu();
4417
+ $scope.toggleCollapsed = function () {
4418
+ $scope.collapsed = !$scope.collapsed;
4419
+ };
3812
4420
  /* isCollapsed and showShortcuts are used to control how the menu is displayed in a responsive environment and whether the shortcut keystrokes help should be displayed */
3813
4421
  $scope.isCollapsed = true;
3814
4422
  $scope.showShortcuts = false;
@@ -3836,8 +4444,8 @@ var fng;
3836
4444
  }
3837
4445
  }
3838
4446
  function filter(event) {
3839
- var tagName = (event.target || event.srcElement).tagName;
3840
- return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
4447
+ var tagName = (event.target).tagName;
4448
+ return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA' || tagName == "DIV" && event.target.classList.contains('ck-editor__editable'));
3841
4449
  }
3842
4450
  //console.log(event.keyCode, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey);
3843
4451
  if (event.keyCode === 191 && (filter(event) || (event.ctrlKey && !event.altKey && !event.metaKey))) {
@@ -3866,7 +4474,7 @@ var fng;
3866
4474
  else if (event.keyCode === 45 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
3867
4475
  deferredBtnClick('newButton'); // Ctrl+Shift+Ins creates New record
3868
4476
  }
3869
- else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && event.altKey && !event.metaKey) {
4477
+ else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
3870
4478
  deferredBtnClick('deleteButton'); // Ctrl+Shift+X deletes record
3871
4479
  }
3872
4480
  };
@@ -3882,6 +4490,11 @@ var fng;
3882
4490
  };
3883
4491
  $scope.$on('fngControllersLoaded', function (evt, sharedData, modelName) {
3884
4492
  $scope.contextMenu = sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false);
4493
+ if (sharedData.dropDownDisplayPromise) {
4494
+ sharedData.dropDownDisplayPromise.then(function (value) {
4495
+ $scope.contextMenu = value;
4496
+ });
4497
+ }
3885
4498
  });
3886
4499
  $scope.$on('fngControllersUnloaded', function (evt) {
3887
4500
  clearContextMenu();
@@ -3897,28 +4510,59 @@ var fng;
3897
4510
  }
3898
4511
  else {
3899
4512
  // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
3900
- var args = item.args || [], fn = item.fn;
3901
- switch (args.length) {
3902
- case 0:
3903
- fn();
3904
- break;
3905
- case 1:
3906
- fn(args[0]);
3907
- break;
3908
- case 2:
3909
- fn(args[0], args[1]);
3910
- break;
3911
- case 3:
3912
- fn(args[0], args[1], args[2]);
3913
- break;
3914
- case 4:
3915
- fn(args[0], args[1], args[2], args[3]);
3916
- break;
4513
+ var args = item.args || [];
4514
+ var fn = item.fn;
4515
+ if (typeof fn === "function") {
4516
+ switch (args.length) {
4517
+ case 0:
4518
+ fn();
4519
+ break;
4520
+ case 1:
4521
+ fn(args[0]);
4522
+ break;
4523
+ case 2:
4524
+ fn(args[0], args[1]);
4525
+ break;
4526
+ case 3:
4527
+ fn(args[0], args[1], args[2]);
4528
+ break;
4529
+ case 4:
4530
+ fn(args[0], args[1], args[2], args[3]);
4531
+ break;
4532
+ }
4533
+ }
4534
+ else if (fn) {
4535
+ throw new Error("Incorrect menu setup");
3917
4536
  }
3918
4537
  }
3919
4538
  };
3920
4539
  $scope.isHidden = function (index) {
3921
- return $scope.items[index].isHidden ? $scope.items[index].isHidden() : false;
4540
+ function explicitlyHidden(item) {
4541
+ return item.isHidden ? item.isHidden() : false;
4542
+ }
4543
+ var dividerHide = false;
4544
+ // Hide a divider if it appears under another
4545
+ if ($scope.items[index].divider) {
4546
+ if (index === 0) {
4547
+ dividerHide = true;
4548
+ }
4549
+ else {
4550
+ var foundVisible = false;
4551
+ var check = index - 1;
4552
+ while (check >= 0 && !dividerHide && !foundVisible) {
4553
+ if ($scope.items[check].divider) {
4554
+ dividerHide = true;
4555
+ }
4556
+ else if (!explicitlyHidden($scope.items[check])) {
4557
+ foundVisible = true;
4558
+ }
4559
+ else {
4560
+ --check;
4561
+ }
4562
+ }
4563
+ }
4564
+ }
4565
+ return dividerHide || explicitlyHidden($scope.items[index]);
3922
4566
  };
3923
4567
  $scope.isDisabled = function (index) {
3924
4568
  return $scope.items[index].isDisabled ? $scope.items[index].isDisabled() : false;
@@ -3937,6 +4581,10 @@ var fng;
3937
4581
  }
3938
4582
  return thisClass;
3939
4583
  };
4584
+ var originalTitle = $window.document.title;
4585
+ $scope.$on('$routeChangeSuccess', function () {
4586
+ $window.document.title = originalTitle;
4587
+ });
3940
4588
  }
3941
4589
  controllers.NavCtrl = NavCtrl;
3942
4590
  })(controllers = fng.controllers || (fng.controllers = {}));
@@ -3977,6 +4625,7 @@ var fng;
3977
4625
  ])
3978
4626
  .controller('BaseCtrl', fng.controllers.BaseCtrl)
3979
4627
  .controller('SaveChangesModalCtrl', fng.controllers.SaveChangesModalCtrl)
4628
+ .controller('LinkCtrl', fng.controllers.LinkCtrl)
3980
4629
  .controller('ModelCtrl', fng.controllers.ModelCtrl)
3981
4630
  .controller('NavCtrl', fng.controllers.NavCtrl)
3982
4631
  .directive('modelControllerDropdown', fng.directives.modelControllerDropdown)
@@ -3988,6 +4637,7 @@ var fng;
3988
4637
  .directive('fngNakedDate', fng.directives.fngNakedDate)
3989
4638
  .filter('camelCase', fng.filters.camelCase)
3990
4639
  .filter('titleCase', fng.filters.titleCase)
4640
+ .filter('extractTimestampFromMongoID', fng.filters.extractTimestampFromMongoID)
3991
4641
  .service('addAllService', fng.services.addAllService)
3992
4642
  .provider('cssFrameworkService', fng.services.cssFrameworkService)
3993
4643
  .provider('routingService', fng.services.routingService)
@@ -4003,11 +4653,15 @@ var fng;
4003
4653
  // expose the library
4004
4654
  var formsAngular = fng.formsAngular;
4005
4655
 
4006
- angular.module('formsAngular').run(['$templateCache', function($templateCache) {$templateCache.put('base-analysis.html','<div ng-controller="AnalysisCtrl">\n <div class="container-fluid page-header report-header">\n <div ng-class="css(\'rowFluid\')">\n <div class="header-lhs col-xs-7 span7">\n <h1>{{ reportSchema.title }}</h1>\n </div>\n <div class="header-rhs col-xs-5 span5">\n <form-input schema="paramSchema" name="paramForm" ng-show="paramSchema" formstyle="horizontalCompact"></form-input>\n </div>\n </div>\n </div>\n <div class="container-fluid page-body report-body">\n <error-display></error-display>\n <div class="row-fluid">\n <div class="gridStyle" ui-grid="gridOptions" ui-grid-selection></div>\n </div>\n </div>\n</div>\n');
4007
- $templateCache.put('base-edit.html','<div ng-controller="BaseCtrl">\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <h4>{{modelNameDisplay}} :\n <span ng-repeat="field in listSchema">{{getListData(record, field.name)}} </span>\n </h4>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <div form-buttons></div>\n </div>\n </div>\n <div class="container-fluid page-body edit-body">\n <error-display></error-display>\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact"></form-input>\n </div>\n <!--{{ formSchema | json }}-->\n</div>\n');
4008
- $templateCache.put('base-list.html','<div ng-controller="BaseCtrl">\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <a ng-href="{{generateNewUrl()}}"><button id="newBtn" class="btn btn-default"><i class="icon-plus"></i> New</button></a>\n </div>\n </div>\n <div class="page-body list-body">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" infinite-scroll="scrollTheList()">\n <a ng-repeat="record in recordList" ng-href="{{generateEditUrl(record)}}">\n <div class="list-item">\n <div ng-class="css(\'span\',12/listSchema.length)" ng-repeat="field in listSchema">{{getListData(record, field.name)}} </div>\n </div>\n </a>\n </div>\n </div>\n</div>\n');
4656
+ angular.module('formsAngular').run(['$templateCache', function($templateCache) {$templateCache.put('base-analysis.html','<div ng-controller="AnalysisCtrl">\n <error-display></error-display>\n <div class="container-fluid page-header report-header">\n <div ng-class="css(\'rowFluid\')">\n <div class="header-lhs col-xs-7 span7">\n <h1>{{ titleWithSubstitutions }}</h1>\n </div>\n <div class="header-rhs col-xs-5 span5">\n <form-input schema="paramSchema" name="paramForm" ng-show="paramSchema" formstyle="horizontalCompact"></form-input>\n </div>\n </div>\n </div>\n <div class="container-fluid page-body report-body">\n <div class="row-fluid report-grow">\n <div class="gridStyle" style="height:100%;" ui-grid="gridOptions" ui-grid-selection ui-grid-resize-columns></div>\n </div>\n </div>\n</div>\n');
4657
+ $templateCache.put('base-edit.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <h1 id="header-text">{{modelNameDisplay}} :\n <span id="header-data-desc">\n <span ng-show="!!editFormHeader" >{{ editFormHeader() }}</span>\n <span ng-hide="!!editFormHeader" ng-repeat="field in listSchema" ng-bind-html="getListData(record, field.name) + \' \'"></span>\n </span>\n </h1>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <div form-buttons></div>\n </div>\n </div>\n <div class="container-fluid page-body edit-body">\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact"></form-input>\n </div>\n<!-- <pre>-->\n <!--Record-->\n <!--{{ record | json }}-->\n <!--formSchema-->\n <!--{{ formSchema | json }}-->\n<!-- </pre>-->\n</div>\n');
4658
+ $templateCache.put('base-list-view.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n </div>\n <div class="page-body list-body">\n <div ng-class="css(\'rowFluid\')" infinite-scroll="scrollTheList()">\n <a ng-repeat="record in recordList" ng-href="{{generateViewUrl(record)}}">\n <div class="list-item">\n <div ng-class="css(\'span\',12/listSchema.length)" ng-repeat="field in listSchema">{{getListData(record, field.name)}} </div>\n </div>\n </a>\n </div>\n </div>\n</div>\n');
4659
+ $templateCache.put('base-list.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <a ng-href="{{generateNewUrl()}}"><button id="newBtn" class="btn btn-default"><i class="icon-plus"></i> New</button></a>\n </div>\n </div>\n <div class="page-body list-body">\n <div ng-class="css(\'rowFluid\')" infinite-scroll="scrollTheList()">\n <a ng-repeat="record in recordList" ng-href="{{generateEditUrl(record)}}">\n <div class="list-item">\n <div ng-class="css(\'span\',12/listSchema.length)" ng-repeat="field in listSchema">{{getListData(record, field.name)}} </div>\n </div>\n </a>\n </div>\n </div>\n</div>\n');
4660
+ $templateCache.put('base-view.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <h1 id="header-text">{{modelNameDisplay}} :\n <span ng-repeat="field in listSchema" ng-bind-html="getListData(record, field.name) + \' \'"></span>\n </h1>\n </div>\n </div>\n <div class="container-fluid page-body edit-body">\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact" viewform="true"></form-input>\n </div>\n</div>\n');
4661
+ $templateCache.put('error-display-bs2.html','<div id="display-error" ng-show="errorVisible" class="row-fluid ng-hide">\n <div class="alert alert-error offset1 span10">\n <button type="button" id="err-hide" class="close" ng-click="dismissError()"><i class="icon-remove"></i></button>\n <button type="button" id="err-pin" class="close" ng-click="stickError()"><i class="icon-eye-open"></i></button>\n <h4 id="err-title">{{alertTitle}}</h4>\n <div id="err-msg" ng-bind-html="errorMessage"></div>\n </div>\n</div>\n');
4662
+ $templateCache.put('error-display-bs3.html','<div id="display-error" ng-show="errorVisible" class="row ng-hide">\n <div class="alert alert-error col-md-offset-1 col-md-10 alert-danger">\n <button type="button" id="err-hide" class="close" ng-click="dismissError()"><i class="glyphicon glyphicon-remove"></i></button>\n <button type="button" id="err-pin" class="close" ng-click="stickError()"><i class="glyphicon glyphicon-pushpin"></i></button>\n <h4 id="err-title">{{alertTitle}}</h4>\n <div id="err-msg" ng-bind-html="errorMessage"></div>\n </div>\n</div>\n');
4009
4663
  $templateCache.put('error-messages.html','<div ng-message="required">A value is required for this field</div>\n<div ng-message="minlength">Too few characters entered</div>\n<div ng-message="maxlength">Too many characters entered</div>\n<div ng-message="min">That value is too small</div>\n<div ng-message="max">That value is too large</div>\n<div ng-message="email">You need to enter a valid email address</div>\n<div ng-message="pattern">This field does not match the expected pattern</div>\n');
4010
- $templateCache.put('form-button-bs2.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-mini btn-primary form-btn" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="icon-ok"></i> Save</button>\n <button id="cancelButton" class="btn btn-mini btn-warning form-btn" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="icon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-mini btn-success form-btn" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="icon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-mini btn-danger form-btn" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="icon-minus"></i> Delete</button>\n </div>\n</div>\n');
4011
- $templateCache.put('form-button-bs3.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-primary form-btn btn-xs" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="glyphicon glyphicon-ok"></i> Save</button>\n <button id="cancelButton" class="btn btn-warning form-btn btn-xs" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="glyphicon glyphicon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-success form-btn btn-xs" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="glyphicon glyphicon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-danger form-btn btn-xs" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="glyphicon glyphicon-minus"></i> Delete</button>\n </div>\n</div>\n');
4012
- $templateCache.put('search-bs2.html','<form class="navbar-search pull-right">\n <div id="search-cg" class="control-group" ng-class="errorClass">\n <input type="text" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <span ng-class="resultClass($index)" ng-click="selectResult($index)">{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');
4013
- $templateCache.put('search-bs3.html','<form class="pull-right navbar-form">\n <div id="search-cg" class="form-group" ng-class="errorClass">\n <input type="text" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query form-control" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <span ng-class="resultClass($index)" ng-click="selectResult($index)" title={{result.additional}}>{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');}]);
4664
+ $templateCache.put('form-button-bs2.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-mini btn-primary form-btn" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="icon-ok"></i> Save</button>\n <div id="why-disabled" ng-class="{showwhy:!!whyDisabled}" ng-bind-html="whyDisabled"></div>\n <button id="cancelButton" class="btn btn-mini btn-warning form-btn" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="icon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-mini btn-success form-btn" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="icon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-mini btn-danger form-btn" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="icon-minus"></i> Delete</button>\n </div>\n</div>\n');
4665
+ $templateCache.put('form-button-bs3.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-primary form-btn btn-xs" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="glyphicon glyphicon-ok"></i> Save</button>\n <div id="why-disabled" ng-class="{showwhy:!!whyDisabled}" ng-bind-html="whyDisabled"></div>\n <button id="cancelButton" class="btn btn-warning form-btn btn-xs" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="glyphicon glyphicon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-success form-btn btn-xs" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="glyphicon glyphicon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-danger form-btn btn-xs" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="glyphicon glyphicon-minus"></i> Delete</button>\n </div>\n</div>\n');
4666
+ $templateCache.put('search-bs2.html','<form class="navbar-search pull-right">\n <div id="search-cg" class="control-group" ng-class="errorClass">\n <input type="text" spellcheck="false" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <a href="{{result.href}}" ng-class="resultClass($index)" title="{{result.additional}}">{{result.resourceText}} {{result.text}}</a>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');
4667
+ $templateCache.put('search-bs3.html','<form class="pull-right navbar-form">\n <div id="search-cg" class="form-group" ng-class="errorClass">\n <input type="text" spellcheck="false" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query form-control" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <a href="{{result.href}}" ng-class="resultClass($index)" title="{{result.additional}}">{{result.resourceText}} {{result.text}}</a>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');}]);